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 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<Document> shared,
Fn<void()> waitingCallback)

View file

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

View file

@ -345,6 +345,10 @@ struct OverlayWidget::PipWrap {
PipWrap(
QWidget *parent,
not_null<DocumentData*> document,
Data::FileOrigin origin,
not_null<DocumentData*> chosenQuality,
HistoryItem *context,
VideoQuality quality,
std::shared_ptr<Streaming::Document> shared,
FnMut<void()> closeAndContinue,
FnMut<void()> destroy);
@ -470,6 +474,10 @@ OverlayWidget::Streamed::Streamed(
OverlayWidget::PipWrap::PipWrap(
QWidget *parent,
not_null<DocumentData*> document,
Data::FileOrigin origin,
not_null<DocumentData*> chosenQuality,
HistoryItem *context,
VideoQuality quality,
std::shared_ptr<Streaming::Document> shared,
FnMut<void()> closeAndContinue,
FnMut<void()> 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<PipWrap>(
_window,
document,
fileOrigin(),
_chosenQuality ? _chosenQuality : document,
_message,
_quality,
_streamed->instance.shared(),
closeAndContinue,
[=] { _pip = nullptr; });

View file

@ -885,18 +885,30 @@ void PipPanel::updateDecorations() {
Pip::Pip(
not_null<Delegate*> delegate,
not_null<DocumentData*> data,
Data::FileOrigin origin,
not_null<DocumentData*> chosenQuality,
HistoryItem *context,
VideoQuality quality,
std::shared_ptr<Streaming::Document> shared,
FnMut<void()> closeAndContinue,
FnMut<void()> 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<PlaybackProgress>())
, _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<const HistoryItem*> data) {
if (_context != data) {
_context = nullptr;
}
}, _panel.rp()->lifetime());
}
}
Pip::~Pip() = default;
std::shared_ptr<Streaming::Document> 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*> 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*> 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(),

View file

@ -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 <QtCore/QPointer>
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*> delegate,
not_null<DocumentData*> data,
Data::FileOrigin origin,
not_null<DocumentData*> chosenQuality,
HistoryItem *context,
VideoQuality quality,
std::shared_ptr<Streaming::Document> shared,
FnMut<void()> closeAndContinue,
FnMut<void()> destroy);
~Pip();
[[nodiscard]] std::shared_ptr<Streaming::Document> 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*> _delegate;
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;
PipPanel _panel;
QSize _size;
@ -260,6 +279,10 @@ private:
std::unique_ptr<PlaybackProgress> _playbackProgress;
std::shared_ptr<Data::DocumentMedia> _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