Support quality change in PiP.

This commit is contained in:
John Preston 2024-11-01 14:15:28 +04:00
parent 3cfa963f69
commit 0971485367
5 changed files with 182 additions and 42 deletions

View file

@ -17,6 +17,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Media { namespace Media {
namespace Streaming { 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( Instance::Instance(
std::shared_ptr<Document> shared, std::shared_ptr<Document> shared,
Fn<void()> waitingCallback) Fn<void()> waitingCallback)

View file

@ -27,6 +27,7 @@ class Player;
class Instance { class Instance {
public: public:
Instance(const Instance &other);
Instance( Instance(
std::shared_ptr<Document> shared, std::shared_ptr<Document> shared,
Fn<void()> waitingCallback); Fn<void()> waitingCallback);

View file

@ -345,6 +345,10 @@ struct OverlayWidget::PipWrap {
PipWrap( PipWrap(
QWidget *parent, QWidget *parent,
not_null<DocumentData*> document, not_null<DocumentData*> document,
Data::FileOrigin origin,
not_null<DocumentData*> chosenQuality,
HistoryItem *context,
VideoQuality quality,
std::shared_ptr<Streaming::Document> shared, std::shared_ptr<Streaming::Document> shared,
FnMut<void()> closeAndContinue, FnMut<void()> closeAndContinue,
FnMut<void()> destroy); FnMut<void()> destroy);
@ -470,6 +474,10 @@ OverlayWidget::Streamed::Streamed(
OverlayWidget::PipWrap::PipWrap( OverlayWidget::PipWrap::PipWrap(
QWidget *parent, QWidget *parent,
not_null<DocumentData*> document, not_null<DocumentData*> document,
Data::FileOrigin origin,
not_null<DocumentData*> chosenQuality,
HistoryItem *context,
VideoQuality quality,
std::shared_ptr<Streaming::Document> shared, std::shared_ptr<Streaming::Document> shared,
FnMut<void()> closeAndContinue, FnMut<void()> closeAndContinue,
FnMut<void()> destroy) FnMut<void()> destroy)
@ -477,6 +485,10 @@ OverlayWidget::PipWrap::PipWrap(
, wrapped( , wrapped(
&delegate, &delegate,
document, document,
origin,
chosenQuality,
context,
quality,
std::move(shared), std::move(shared),
std::move(closeAndContinue), std::move(closeAndContinue),
std::move(destroy)) { std::move(destroy)) {
@ -3849,10 +3861,14 @@ bool OverlayWidget::initStreaming(const StartStreaming &startStreaming) {
}); });
}, _streamed->instance.lifetime()); }, _streamed->instance.lifetime());
const auto continuing = startStreaming.continueStreaming
&& _pip
&& (_pip->wrapped.shared().get()
== _streamed->instance.shared().get());
if (startStreaming.continueStreaming) { if (startStreaming.continueStreaming) {
_pip = nullptr; _pip = nullptr;
} }
if (!startStreaming.continueStreaming if (!continuing
|| (!_streamed->instance.player().active() || (!_streamed->instance.player().active()
&& !_streamed->instance.player().finished())) { && !_streamed->instance.player().finished())) {
startStreamingPlayer(startStreaming); startStreamingPlayer(startStreaming);
@ -4517,6 +4533,10 @@ void OverlayWidget::switchToPip() {
_pip = std::make_unique<PipWrap>( _pip = std::make_unique<PipWrap>(
_window, _window,
document, document,
fileOrigin(),
_chosenQuality ? _chosenQuality : document,
_message,
_quality,
_streamed->instance.shared(), _streamed->instance.shared(),
closeAndContinue, closeAndContinue,
[=] { _pip = nullptr; }); [=] { _pip = nullptr; });

View file

@ -885,18 +885,30 @@ void PipPanel::updateDecorations() {
Pip::Pip( Pip::Pip(
not_null<Delegate*> delegate, not_null<Delegate*> delegate,
not_null<DocumentData*> data, not_null<DocumentData*> data,
Data::FileOrigin origin,
not_null<DocumentData*> chosenQuality,
HistoryItem *context,
VideoQuality quality,
std::shared_ptr<Streaming::Document> shared, std::shared_ptr<Streaming::Document> shared,
FnMut<void()> closeAndContinue, FnMut<void()> closeAndContinue,
FnMut<void()> destroy) FnMut<void()> destroy)
: _delegate(delegate) : _delegate(delegate)
, _data(data) , _data(data)
, _instance(std::move(shared), [=] { waitingAnimationCallback(); }) , _origin(origin)
, _chosenQuality(chosenQuality)
, _context(context)
, _quality(quality)
, _instance(
std::in_place,
std::move(shared),
[=] { waitingAnimationCallback(); })
, _panel( , _panel(
_delegate->pipParentWidget(), _delegate->pipParentWidget(),
[=](Ui::GL::Capabilities capabilities) { [=](Ui::GL::Capabilities capabilities) {
return chooseRenderer(capabilities); return chooseRenderer(capabilities);
}) })
, _playbackProgress(std::make_unique<PlaybackProgress>()) , _playbackProgress(std::make_unique<PlaybackProgress>())
, _dataMedia(_data->createMediaView())
, _rotation(data->owner().mediaRotation().get(data)) , _rotation(data->owner().mediaRotation().get(data))
, _lastPositiveVolume((Core::App().settings().videoVolume() > 0.) , _lastPositiveVolume((Core::App().settings().videoVolume() > 0.)
? Core::App().settings().videoVolume() ? Core::App().settings().videoVolume()
@ -911,15 +923,28 @@ Pip::Pip(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_destroy(); _destroy();
}, _panel.rp()->lifetime()); }, _panel.rp()->lifetime());
if (_context) {
_data->owner().itemRemoved(
) | rpl::start_with_next([=](not_null<const HistoryItem*> data) {
if (_context != data) {
_context = nullptr;
}
}, _panel.rp()->lifetime());
}
} }
Pip::~Pip() = default; Pip::~Pip() = default;
std::shared_ptr<Streaming::Document> Pip::shared() const {
return _instance->shared();
}
void Pip::setupPanel() { void Pip::setupPanel() {
_panel.init(); _panel.init();
const auto size = [&] { const auto size = [&] {
if (!_instance.info().video.size.isEmpty()) { if (!_instance->info().video.size.isEmpty()) {
return _instance.info().video.size; return _instance->info().video.size;
} }
const auto media = _data->activeMediaView(); const auto media = _data->activeMediaView();
if (media) { if (media) {
@ -1138,8 +1163,8 @@ void Pip::seekProgress(float64 value) {
_lastDurationMs); _lastDurationMs);
if (_seekPositionMs != positionMs) { if (_seekPositionMs != positionMs) {
_seekPositionMs = positionMs; _seekPositionMs = positionMs;
if (!_instance.player().paused() if (!_instance->player().paused()
&& !_instance.player().finished()) { && !_instance->player().finished()) {
_pausedBySeek = true; _pausedBySeek = true;
playbackPauseResume(); playbackPauseResume();
} }
@ -1157,7 +1182,7 @@ void Pip::seekFinish(float64 value) {
crl::time(0), crl::time(0),
_lastDurationMs); _lastDurationMs);
_seekPositionMs = -1; _seekPositionMs = -1;
_startPaused = !_pausedBySeek && !_instance.player().finished(); _startPaused = !_pausedBySeek && !_instance->player().finished();
restartAtSeekPosition(positionMs); restartAtSeekPosition(positionMs);
} }
@ -1294,18 +1319,71 @@ void Pip::updatePlayPauseResumeState(const Player::TrackState &state) {
} }
void Pip::setupStreaming() { void Pip::setupStreaming() {
_instance.setPriority(kPipLoaderPriority); _instance->setPriority(kPipLoaderPriority);
_instance.lockPlayer(); _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) { ) | rpl::start_with_next_error([=](Streaming::Update &&update) {
handleStreamingUpdate(std::move(update)); handleStreamingUpdate(std::move(update));
}, [=](Streaming::Error &&error) { }, [=](Streaming::Error &&error) {
handleStreamingError(std::move(error)); handleStreamingError(std::move(error));
}, _instance.lifetime()); }, _instance->lifetime());
updatePlaybackState(); 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::ChosenRenderer Pip::chooseRenderer(
Ui::GL::Capabilities capabilities) { Ui::GL::Capabilities capabilities) {
const auto use = Platform::IsMac() const auto use = Platform::IsMac()
@ -1336,12 +1414,12 @@ void Pip::paint(not_null<Renderer*> renderer) const {
.fade = controlsShown, .fade = controlsShown,
.outer = _panel.widget()->size(), .outer = _panel.widget()->size(),
.rotation = _rotation, .rotation = _rotation,
.videoRotation = _instance.info().video.rotation, .videoRotation = _instance->info().video.rotation,
.useTransparency = _panel.useTransparency(), .useTransparency = _panel.useTransparency(),
}; };
if (canUseVideoFrame()) { if (canUseVideoFrame()) {
renderer->paintTransformedVideoFrame(geometry); renderer->paintTransformedVideoFrame(geometry);
_instance.markFrameShown(); _instance->markFrameShown();
} else { } else {
const auto content = staticContent(); const auto content = staticContent();
if (_preparedCoverState == ThumbState::Cover) { if (_preparedCoverState == ThumbState::Cover) {
@ -1349,7 +1427,7 @@ void Pip::paint(not_null<Renderer*> renderer) const {
} }
renderer->paintTransformedStaticContent(content, geometry); renderer->paintTransformedStaticContent(content, geometry);
} }
if (_instance.waitingShown()) { if (_instance->waitingShown()) {
renderer->paintRadialLoading(countRadialRect(), controlsShown); renderer->paintRadialLoading(countRadialRect(), controlsShown);
} }
if (controlsShown > 0) { if (controlsShown > 0) {
@ -1535,12 +1613,14 @@ void Pip::handleStreamingUpdate(Streaming::Update &&update) {
v::match(update.data, [&](const Information &update) { v::match(update.data, [&](const Information &update) {
_panel.setAspectRatio( _panel.setAspectRatio(
FlipSizeByRotation(update.video.size, _rotation)); FlipSizeByRotation(update.video.size, _rotation));
_qualityChangeFrame = QImage();
}, [&](PreloadedVideo) { }, [&](PreloadedVideo) {
updatePlaybackState(); updatePlaybackState();
}, [&](UpdateVideo) { }, [&](UpdateVideo update) {
_panel.update(); _panel.update();
Core::App().updateNonIdle(); Core::App().updateNonIdle();
updatePlaybackState(); updatePlaybackState();
_lastUpdatePosition = update.position;
}, [&](PreloadedAudio) { }, [&](PreloadedAudio) {
updatePlaybackState(); updatePlaybackState();
}, [&](UpdateAudio) { }, [&](UpdateAudio) {
@ -1554,7 +1634,7 @@ void Pip::handleStreamingUpdate(Streaming::Update &&update) {
} }
void Pip::updatePlaybackState() { void Pip::updatePlaybackState() {
const auto state = _instance.player().prepareLegacyState(); const auto state = _instance->player().prepareLegacyState();
updatePlayPauseResumeState(state); updatePlayPauseResumeState(state);
if (state.position == kTimeUnknown if (state.position == kTimeUnknown
|| state.length == kTimeUnknown || state.length == kTimeUnknown
@ -1619,61 +1699,65 @@ void Pip::handleStreamingError(Streaming::Error &&error) {
} }
void Pip::playbackPauseResume() { void Pip::playbackPauseResume() {
if (_instance.player().failed()) { if (_instance->player().failed()) {
_panel.widget()->close(); _panel.widget()->close();
} else if (_instance.player().finished() } else if (_instance->player().finished()
|| !_instance.player().active()) { || !_instance->player().active()) {
_startPaused = false; _startPaused = false;
restartAtSeekPosition(0); restartAtSeekPosition(0);
} else if (_instance.player().paused()) { } else if (_instance->player().paused()) {
_instance.resume(); _instance->resume();
updatePlaybackState(); updatePlaybackState();
} else { } else {
_instance.pause(); _instance->pause();
updatePlaybackState(); updatePlaybackState();
} }
} }
void Pip::restartAtSeekPosition(crl::time position) { void Pip::restartAtSeekPosition(crl::time position) {
if (!_instance.info().video.cover.isNull()) { _lastUpdatePosition = position;
if (!_instance->info().video.cover.isNull()) {
_preparedCoverStorage = QImage(); _preparedCoverStorage = QImage();
_preparedCoverState = ThumbState::Empty; _preparedCoverState = ThumbState::Empty;
_instance.saveFrameToCover(); _instance->saveFrameToCover();
} }
auto options = Streaming::PlaybackOptions(); auto options = Streaming::PlaybackOptions();
options.position = position; options.position = position;
options.hwAllowed = Core::App().settings().hardwareAcceleratedVideo(); options.hwAllowed = Core::App().settings().hardwareAcceleratedVideo();
options.audioId = _instance.player().prepareLegacyState().id; options.audioId = _instance->player().prepareLegacyState().id;
options.speed = _delegate->pipPlaybackSpeed(); options.speed = _delegate->pipPlaybackSpeed();
_instance.play(options); _instance->play(options);
if (_startPaused) { if (_startPaused) {
_instance.pause(); _instance->pause();
} }
_pausedBySeek = false; _pausedBySeek = false;
updatePlaybackState(); updatePlaybackState();
} }
bool Pip::canUseVideoFrame() const { bool Pip::canUseVideoFrame() const {
return _instance.player().ready() return _instance->player().ready()
&& !_instance.info().video.cover.isNull(); && !_instance->info().video.cover.isNull();
} }
QImage Pip::videoFrame(const FrameRequest &request) const { QImage Pip::videoFrame(const FrameRequest &request) const {
Expects(canUseVideoFrame()); Expects(canUseVideoFrame());
return _instance.frame(request); return _instance->frame(request);
} }
Streaming::FrameWithInfo Pip::videoFrameWithInfo() const { Streaming::FrameWithInfo Pip::videoFrameWithInfo() const {
Expects(canUseVideoFrame()); Expects(canUseVideoFrame());
return _instance.frameWithInfo(); return _instance->frameWithInfo();
} }
QImage Pip::staticContent() const { 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 media = _data->activeMediaView();
const auto use = media const auto use = media
? media ? media
@ -1701,7 +1785,7 @@ QImage Pip::staticContent() const {
} }
_preparedCoverState = state; _preparedCoverState = state;
if (state == ThumbState::Cover) { if (state == ThumbState::Cover) {
_preparedCoverStorage = _instance.info().video.cover; _preparedCoverStorage = cover;
} else { } else {
_preparedCoverStorage = (good _preparedCoverStorage = (good
? good ? good
@ -1727,7 +1811,7 @@ void Pip::paintRadialLoadingContent(
st::radialLine, st::radialLine,
st::radialLine, st::radialLine,
st::radialLine)); st::radialLine));
p.setOpacity(_instance.waitingOpacity()); p.setOpacity(_instance->waitingOpacity());
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(st::radialBg); p.setBrush(st::radialBg);
{ {
@ -1737,7 +1821,7 @@ void Pip::paintRadialLoadingContent(
p.setOpacity(1.); p.setOpacity(1.);
Ui::InfiniteRadialAnimation::Draw( Ui::InfiniteRadialAnimation::Draw(
p, p,
_instance.waitingState(), _instance->waitingState(),
arc.topLeft(), arc.topLeft(),
arc.size(), arc.size(),
_panel.widget()->width(), _panel.widget()->width(),

View file

@ -7,13 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "data/data_file_origin.h"
#include "media/streaming/media_streaming_instance.h" #include "media/streaming/media_streaming_instance.h"
#include "media/media_common.h"
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/round_rect.h" #include "ui/round_rect.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
#include <QtCore/QPointer> #include <QtCore/QPointer>
class HistoryItem;
namespace base { namespace base {
class PowerSaveBlocker; class PowerSaveBlocker;
} // namespace base } // namespace base
@ -32,12 +36,15 @@ struct Capabilities;
} // namespace GL } // namespace GL
} // namespace Ui } // namespace Ui
namespace Media { namespace Media::Player {
namespace Player {
struct TrackState; struct TrackState;
} // namespace Player } // namespace Media::Player
namespace View { namespace Media::Streaming {
class Document;
} // namespace Media::Streaming
namespace Media::View {
class PlaybackProgress; class PlaybackProgress;
@ -134,11 +141,17 @@ public:
Pip( Pip(
not_null<Delegate*> delegate, not_null<Delegate*> delegate,
not_null<DocumentData*> data, not_null<DocumentData*> data,
Data::FileOrigin origin,
not_null<DocumentData*> chosenQuality,
HistoryItem *context,
VideoQuality quality,
std::shared_ptr<Streaming::Document> shared, std::shared_ptr<Streaming::Document> shared,
FnMut<void()> closeAndContinue, FnMut<void()> closeAndContinue,
FnMut<void()> destroy); FnMut<void()> destroy);
~Pip(); ~Pip();
[[nodiscard]] std::shared_ptr<Streaming::Document> shared() const;
private: private:
enum class OverState { enum class OverState {
None, None,
@ -245,6 +258,8 @@ private:
QRect outer, QRect outer,
float64 shown) const; float64 shown) const;
[[nodiscard]] QRect countRadialRect() const; [[nodiscard]] QRect countRadialRect() const;
void applyVideoQuality(VideoQuality value);
[[nodiscard]] QImage currentVideoFrameImage() const;
void seekUpdate(QPoint position); void seekUpdate(QPoint position);
void seekProgress(float64 value); void seekProgress(float64 value);
@ -252,7 +267,11 @@ private:
const not_null<Delegate*> _delegate; const not_null<Delegate*> _delegate;
const not_null<DocumentData*> _data; const not_null<DocumentData*> _data;
Streaming::Instance _instance; const Data::FileOrigin _origin;
DocumentData *_chosenQuality = nullptr;
HistoryItem *_context = nullptr;
Media::VideoQuality _quality;
std::optional<Streaming::Instance> _instance;
bool _opengl = false; bool _opengl = false;
PipPanel _panel; PipPanel _panel;
QSize _size; QSize _size;
@ -260,6 +279,10 @@ private:
std::unique_ptr<PlaybackProgress> _playbackProgress; std::unique_ptr<PlaybackProgress> _playbackProgress;
std::shared_ptr<Data::DocumentMedia> _dataMedia; std::shared_ptr<Data::DocumentMedia> _dataMedia;
QImage _qualityChangeFrame;
bool _qualityChangeFinished = false;
crl::time _lastUpdatePosition = 0;
bool _showPause = false; bool _showPause = false;
bool _startPaused = false; bool _startPaused = false;
bool _pausedBySeek = false; bool _pausedBySeek = false;
@ -288,5 +311,4 @@ private:
}; };
} // namespace View } // namespace Media::View
} // namespace Media