From 0971485367a2af33af9b8446f4d71f01abb62db7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 1 Nov 2024 14:15:28 +0400 Subject: [PATCH] Support quality change in PiP. --- .../streaming/media_streaming_instance.cpp | 13 ++ .../streaming/media_streaming_instance.h | 1 + .../media/view/media_view_overlay_widget.cpp | 22 ++- .../SourceFiles/media/view/media_view_pip.cpp | 152 ++++++++++++++---- .../SourceFiles/media/view/media_view_pip.h | 36 ++++- 5 files changed, 182 insertions(+), 42 deletions(-) diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp index f73efed10..4d4149ce3 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp @@ -17,6 +17,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Media { namespace Streaming { +Instance::Instance(const Instance &other) +: _shared(other._shared) +, _waitingCallback(other._waitingCallback) +, _priority(other._priority) +, _playerLocked(other._playerLocked) { + if (_shared) { + _shared->registerInstance(this); + if (_playerLocked) { + _shared->player().lock(); + } + } +} + Instance::Instance( std::shared_ptr shared, Fn waitingCallback) diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_instance.h b/Telegram/SourceFiles/media/streaming/media_streaming_instance.h index 9a7cfd9c3..314779401 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_instance.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_instance.h @@ -27,6 +27,7 @@ class Player; class Instance { public: + Instance(const Instance &other); Instance( std::shared_ptr shared, Fn waitingCallback); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 27113c355..d069604a6 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -345,6 +345,10 @@ struct OverlayWidget::PipWrap { PipWrap( QWidget *parent, not_null document, + Data::FileOrigin origin, + not_null chosenQuality, + HistoryItem *context, + VideoQuality quality, std::shared_ptr shared, FnMut closeAndContinue, FnMut destroy); @@ -470,6 +474,10 @@ OverlayWidget::Streamed::Streamed( OverlayWidget::PipWrap::PipWrap( QWidget *parent, not_null document, + Data::FileOrigin origin, + not_null chosenQuality, + HistoryItem *context, + VideoQuality quality, std::shared_ptr shared, FnMut closeAndContinue, FnMut destroy) @@ -477,6 +485,10 @@ OverlayWidget::PipWrap::PipWrap( , wrapped( &delegate, document, + origin, + chosenQuality, + context, + quality, std::move(shared), std::move(closeAndContinue), std::move(destroy)) { @@ -3849,10 +3861,14 @@ bool OverlayWidget::initStreaming(const StartStreaming &startStreaming) { }); }, _streamed->instance.lifetime()); + const auto continuing = startStreaming.continueStreaming + && _pip + && (_pip->wrapped.shared().get() + == _streamed->instance.shared().get()); if (startStreaming.continueStreaming) { _pip = nullptr; } - if (!startStreaming.continueStreaming + if (!continuing || (!_streamed->instance.player().active() && !_streamed->instance.player().finished())) { startStreamingPlayer(startStreaming); @@ -4517,6 +4533,10 @@ void OverlayWidget::switchToPip() { _pip = std::make_unique( _window, document, + fileOrigin(), + _chosenQuality ? _chosenQuality : document, + _message, + _quality, _streamed->instance.shared(), closeAndContinue, [=] { _pip = nullptr; }); diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index 5399ccef9..94e44cb99 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -885,18 +885,30 @@ void PipPanel::updateDecorations() { Pip::Pip( not_null delegate, not_null data, + Data::FileOrigin origin, + not_null chosenQuality, + HistoryItem *context, + VideoQuality quality, std::shared_ptr shared, FnMut closeAndContinue, FnMut destroy) : _delegate(delegate) , _data(data) -, _instance(std::move(shared), [=] { waitingAnimationCallback(); }) +, _origin(origin) +, _chosenQuality(chosenQuality) +, _context(context) +, _quality(quality) +, _instance( + std::in_place, + std::move(shared), + [=] { waitingAnimationCallback(); }) , _panel( _delegate->pipParentWidget(), [=](Ui::GL::Capabilities capabilities) { return chooseRenderer(capabilities); }) , _playbackProgress(std::make_unique()) +, _dataMedia(_data->createMediaView()) , _rotation(data->owner().mediaRotation().get(data)) , _lastPositiveVolume((Core::App().settings().videoVolume() > 0.) ? Core::App().settings().videoVolume() @@ -911,15 +923,28 @@ Pip::Pip( ) | rpl::start_with_next([=] { _destroy(); }, _panel.rp()->lifetime()); + + if (_context) { + _data->owner().itemRemoved( + ) | rpl::start_with_next([=](not_null data) { + if (_context != data) { + _context = nullptr; + } + }, _panel.rp()->lifetime()); + } } Pip::~Pip() = default; +std::shared_ptr Pip::shared() const { + return _instance->shared(); +} + void Pip::setupPanel() { _panel.init(); const auto size = [&] { - if (!_instance.info().video.size.isEmpty()) { - return _instance.info().video.size; + if (!_instance->info().video.size.isEmpty()) { + return _instance->info().video.size; } const auto media = _data->activeMediaView(); if (media) { @@ -1138,8 +1163,8 @@ void Pip::seekProgress(float64 value) { _lastDurationMs); if (_seekPositionMs != positionMs) { _seekPositionMs = positionMs; - if (!_instance.player().paused() - && !_instance.player().finished()) { + if (!_instance->player().paused() + && !_instance->player().finished()) { _pausedBySeek = true; playbackPauseResume(); } @@ -1157,7 +1182,7 @@ void Pip::seekFinish(float64 value) { crl::time(0), _lastDurationMs); _seekPositionMs = -1; - _startPaused = !_pausedBySeek && !_instance.player().finished(); + _startPaused = !_pausedBySeek && !_instance->player().finished(); restartAtSeekPosition(positionMs); } @@ -1294,18 +1319,71 @@ void Pip::updatePlayPauseResumeState(const Player::TrackState &state) { } void Pip::setupStreaming() { - _instance.setPriority(kPipLoaderPriority); - _instance.lockPlayer(); + _instance->setPriority(kPipLoaderPriority); + _instance->lockPlayer(); - _instance.player().updates( + _instance->switchQualityRequests( + ) | rpl::filter([=](int quality) { + return !_quality.manual && _quality.height != quality; + }) | rpl::start_with_next([=](int quality) { + applyVideoQuality({ + .manual = 0, + .height = uint32(quality), + }); + }, _instance->lifetime()); + + _instance->player().updates( ) | rpl::start_with_next_error([=](Streaming::Update &&update) { handleStreamingUpdate(std::move(update)); }, [=](Streaming::Error &&error) { handleStreamingError(std::move(error)); - }, _instance.lifetime()); + }, _instance->lifetime()); updatePlaybackState(); } +void Pip::applyVideoQuality(VideoQuality value) { + if (_quality == value + || !_dataMedia->canBePlayed(_context)) { + return; + } + const auto resolved = _data->chooseQuality(_context, value); + if (_chosenQuality == resolved) { + return; + } + auto instance = Streaming::Instance( + resolved, + _data, + _context, + _origin, + [=] { waitingAnimationCallback(); }); + if (!instance.valid()) { + return; + } + + if (_instance->ready()) { + _qualityChangeFrame = currentVideoFrameImage(); + } + if (!_instance->player().active() + || _instance->player().finished()) { + _qualityChangeFinished = true; + } + _startPaused = _qualityChangeFinished || _instance->player().paused(); + + _quality = value; + Core::App().settings().setVideoQuality(value); + Core::App().saveSettingsDelayed(); + _chosenQuality = resolved; + _instance.emplace(std::move(instance)); + setupStreaming(); + restartAtSeekPosition(_lastUpdatePosition); +} + +QImage Pip::currentVideoFrameImage() const { + return _instance->player().ready() + ? _instance->player().currentFrameImage() + : _instance->info().video.cover; +} + Ui::GL::ChosenRenderer Pip::chooseRenderer( Ui::GL::Capabilities capabilities) { const auto use = Platform::IsMac() @@ -1336,12 +1414,12 @@ void Pip::paint(not_null renderer) const { .fade = controlsShown, .outer = _panel.widget()->size(), .rotation = _rotation, - .videoRotation = _instance.info().video.rotation, + .videoRotation = _instance->info().video.rotation, .useTransparency = _panel.useTransparency(), }; if (canUseVideoFrame()) { renderer->paintTransformedVideoFrame(geometry); - _instance.markFrameShown(); + _instance->markFrameShown(); } else { const auto content = staticContent(); if (_preparedCoverState == ThumbState::Cover) { @@ -1349,7 +1427,7 @@ void Pip::paint(not_null renderer) const { } renderer->paintTransformedStaticContent(content, geometry); } - if (_instance.waitingShown()) { + if (_instance->waitingShown()) { renderer->paintRadialLoading(countRadialRect(), controlsShown); } if (controlsShown > 0) { @@ -1535,12 +1613,14 @@ void Pip::handleStreamingUpdate(Streaming::Update &&update) { v::match(update.data, [&](const Information &update) { _panel.setAspectRatio( FlipSizeByRotation(update.video.size, _rotation)); + _qualityChangeFrame = QImage(); }, [&](PreloadedVideo) { updatePlaybackState(); - }, [&](UpdateVideo) { + }, [&](UpdateVideo update) { _panel.update(); Core::App().updateNonIdle(); updatePlaybackState(); + _lastUpdatePosition = update.position; }, [&](PreloadedAudio) { updatePlaybackState(); }, [&](UpdateAudio) { @@ -1554,7 +1634,7 @@ void Pip::handleStreamingUpdate(Streaming::Update &&update) { } void Pip::updatePlaybackState() { - const auto state = _instance.player().prepareLegacyState(); + const auto state = _instance->player().prepareLegacyState(); updatePlayPauseResumeState(state); if (state.position == kTimeUnknown || state.length == kTimeUnknown @@ -1619,61 +1699,65 @@ void Pip::handleStreamingError(Streaming::Error &&error) { } void Pip::playbackPauseResume() { - if (_instance.player().failed()) { + if (_instance->player().failed()) { _panel.widget()->close(); - } else if (_instance.player().finished() - || !_instance.player().active()) { + } else if (_instance->player().finished() + || !_instance->player().active()) { _startPaused = false; restartAtSeekPosition(0); - } else if (_instance.player().paused()) { - _instance.resume(); + } else if (_instance->player().paused()) { + _instance->resume(); updatePlaybackState(); } else { - _instance.pause(); + _instance->pause(); updatePlaybackState(); } } void Pip::restartAtSeekPosition(crl::time position) { - if (!_instance.info().video.cover.isNull()) { + _lastUpdatePosition = position; + + if (!_instance->info().video.cover.isNull()) { _preparedCoverStorage = QImage(); _preparedCoverState = ThumbState::Empty; - _instance.saveFrameToCover(); + _instance->saveFrameToCover(); } auto options = Streaming::PlaybackOptions(); options.position = position; options.hwAllowed = Core::App().settings().hardwareAcceleratedVideo(); - options.audioId = _instance.player().prepareLegacyState().id; + options.audioId = _instance->player().prepareLegacyState().id; options.speed = _delegate->pipPlaybackSpeed(); - _instance.play(options); + _instance->play(options); if (_startPaused) { - _instance.pause(); + _instance->pause(); } _pausedBySeek = false; updatePlaybackState(); } bool Pip::canUseVideoFrame() const { - return _instance.player().ready() - && !_instance.info().video.cover.isNull(); + return _instance->player().ready() + && !_instance->info().video.cover.isNull(); } QImage Pip::videoFrame(const FrameRequest &request) const { Expects(canUseVideoFrame()); - return _instance.frame(request); + return _instance->frame(request); } Streaming::FrameWithInfo Pip::videoFrameWithInfo() const { Expects(canUseVideoFrame()); - return _instance.frameWithInfo(); + return _instance->frameWithInfo(); } QImage Pip::staticContent() const { - const auto &cover = _instance.info().video.cover; + const auto &cover = !_qualityChangeFrame.isNull() + ? _qualityChangeFrame + : _instance->info().video.cover; const auto media = _data->activeMediaView(); const auto use = media ? media @@ -1701,7 +1785,7 @@ QImage Pip::staticContent() const { } _preparedCoverState = state; if (state == ThumbState::Cover) { - _preparedCoverStorage = _instance.info().video.cover; + _preparedCoverStorage = cover; } else { _preparedCoverStorage = (good ? good @@ -1727,7 +1811,7 @@ void Pip::paintRadialLoadingContent( st::radialLine, st::radialLine, st::radialLine)); - p.setOpacity(_instance.waitingOpacity()); + p.setOpacity(_instance->waitingOpacity()); p.setPen(Qt::NoPen); p.setBrush(st::radialBg); { @@ -1737,7 +1821,7 @@ void Pip::paintRadialLoadingContent( p.setOpacity(1.); Ui::InfiniteRadialAnimation::Draw( p, - _instance.waitingState(), + _instance->waitingState(), arc.topLeft(), arc.size(), _panel.widget()->width(), diff --git a/Telegram/SourceFiles/media/view/media_view_pip.h b/Telegram/SourceFiles/media/view/media_view_pip.h index d0156e1bd..503973896 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.h +++ b/Telegram/SourceFiles/media/view/media_view_pip.h @@ -7,13 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "data/data_file_origin.h" #include "media/streaming/media_streaming_instance.h" +#include "media/media_common.h" #include "ui/effects/animations.h" #include "ui/round_rect.h" #include "ui/rp_widget.h" #include +class HistoryItem; + namespace base { class PowerSaveBlocker; } // namespace base @@ -32,12 +36,15 @@ struct Capabilities; } // namespace GL } // namespace Ui -namespace Media { -namespace Player { +namespace Media::Player { struct TrackState; -} // namespace Player +} // namespace Media::Player -namespace View { +namespace Media::Streaming { +class Document; +} // namespace Media::Streaming + +namespace Media::View { class PlaybackProgress; @@ -134,11 +141,17 @@ public: Pip( not_null delegate, not_null data, + Data::FileOrigin origin, + not_null chosenQuality, + HistoryItem *context, + VideoQuality quality, std::shared_ptr shared, FnMut closeAndContinue, FnMut destroy); ~Pip(); + [[nodiscard]] std::shared_ptr shared() const; + private: enum class OverState { None, @@ -245,6 +258,8 @@ private: QRect outer, float64 shown) const; [[nodiscard]] QRect countRadialRect() const; + void applyVideoQuality(VideoQuality value); + [[nodiscard]] QImage currentVideoFrameImage() const; void seekUpdate(QPoint position); void seekProgress(float64 value); @@ -252,7 +267,11 @@ private: const not_null _delegate; const not_null _data; - Streaming::Instance _instance; + const Data::FileOrigin _origin; + DocumentData *_chosenQuality = nullptr; + HistoryItem *_context = nullptr; + Media::VideoQuality _quality; + std::optional _instance; bool _opengl = false; PipPanel _panel; QSize _size; @@ -260,6 +279,10 @@ private: std::unique_ptr _playbackProgress; std::shared_ptr _dataMedia; + QImage _qualityChangeFrame; + bool _qualityChangeFinished = false; + crl::time _lastUpdatePosition = 0; + bool _showPause = false; bool _startPaused = false; bool _pausedBySeek = false; @@ -288,5 +311,4 @@ private: }; -} // namespace View -} // namespace Media +} // namespace Media::View