Allow switching video quality.

This commit is contained in:
John Preston 2024-10-25 14:26:41 +04:00
parent 7ba78540ac
commit 3f2f3ebd51
9 changed files with 227 additions and 46 deletions

View File

@ -29,6 +29,7 @@ MediaPlayerButton {
MediaSpeedMenu {
dropdown: DropdownMenu;
qualityMenu: Menu;
activeCheck: icon;
activeCheckSkip: pixels;
sliderStyle: TextStyle;
@ -165,16 +166,20 @@ mediaPlayerMenu: DropdownMenu(defaultDropdownMenu) {
}
mediaPlayerMenuCheck: icon {{ "player/player_check", mediaPlayerActiveFg }};
mediaPlayerSpeedMenuInner: Menu(menuWithIcons) {
separator: MenuSeparator(defaultMenuSeparator) {
padding: margins(0px, 4px, 0px, 4px);
width: 6px;
}
itemPadding: margins(54px, 7px, 54px, 9px);
itemFgDisabled: mediaPlayerActiveFg;
}
mediaPlayerSpeedMenu: MediaSpeedMenu {
dropdown: DropdownMenu(mediaPlayerMenu) {
menu: Menu(menuWithIcons) {
separator: MenuSeparator(defaultMenuSeparator) {
padding: margins(0px, 4px, 0px, 4px);
width: 6px;
}
itemPadding: margins(54px, 7px, 54px, 9px);
itemFgDisabled: mediaPlayerActiveFg;
}
menu: mediaPlayerSpeedMenuInner;
}
qualityMenu: Menu(mediaPlayerSpeedMenuInner) {
itemPadding: margins(17px, 7px, 54px, 9px);
}
activeCheck: mediaPlayerMenuCheck;
activeCheckSkip: 8px;

View File

@ -177,7 +177,8 @@ void FillSpeedMenu(
not_null<Ui::Menu::Menu*> menu,
const style::MediaSpeedMenu &st,
rpl::producer<float64> value,
Fn<void(float64)> callback) {
Fn<void(float64)> callback,
bool onlySlider) {
auto slider = base::make_unique_q<SpeedSliderItem>(
menu,
st,
@ -198,6 +199,11 @@ void FillSpeedMenu(
));
menu->addAction(std::move(slider));
if (onlySlider) {
return;
}
menu->addSeparator(&st.dropdown.menu.separator);
struct SpeedPoint {
@ -693,7 +699,10 @@ SpeedController::SpeedController(
not_null<QWidget*> menuParent,
Fn<void(bool)> menuOverCallback,
Fn<float64(bool lastNonDefault)> value,
Fn<void(float64)> change)
Fn<void(float64)> change,
std::vector<int> qualities,
Fn<int()> quality,
Fn<void(int)> changeQuality)
: WithDropdownController(
button,
menuParent,
@ -702,7 +711,12 @@ SpeedController::SpeedController(
std::move(menuOverCallback))
, _st(button->st())
, _lookup(std::move(value))
, _change(std::move(change)) {
, _change(std::move(change))
, _qualities(std::move(qualities))
, _lookupQuality(std::move(quality))
, _changeQuality(std::move(changeQuality)) {
Expects(_qualities.empty() || (_lookupQuality && _changeQuality));
button->setClickedCallback([=] {
toggleDefault();
save();
@ -756,12 +770,63 @@ void SpeedController::save() {
_saved.fire({});
}
void SpeedController::setQuality(int quality) {
_quality = quality;
_changeQuality(quality);
}
void SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
FillSpeedMenu(
menu->menu(),
_st.menu,
_speedChanged.events_starting_with(speed()),
[=](float64 speed) { setSpeed(speed); save(); });
[=](float64 speed) { setSpeed(speed); save(); },
!_qualities.empty());
if (_qualities.empty()) {
return;
}
_quality = _lookupQuality();
const auto raw = menu->menu();
const auto &st = _st.menu;
raw->addSeparator(&st.dropdown.menu.separator);
const auto add = [&](int quality) {
const auto text = quality ? u"%1p"_q.arg(quality) : u"Original"_q;
auto action = base::make_unique_q<Ui::Menu::Action>(
raw,
st.qualityMenu,
Ui::Menu::CreateAction(raw, text, [=] { _changeQuality(quality); }),
nullptr,
nullptr);
const auto raw = action.get();
const auto check = Ui::CreateChild<Ui::RpWidget>(raw);
check->resize(st.activeCheck.size());
check->paintRequest(
) | rpl::start_with_next([check, icon = &st.activeCheck] {
auto p = QPainter(check);
icon->paint(p, 0, 0, check->width());
}, check->lifetime());
raw->sizeValue(
) | rpl::start_with_next([=, skip = st.activeCheckSkip](QSize size) {
check->moveToRight(
skip,
(size.height() - check->height()) / 2,
size.width());
}, check->lifetime());
check->setAttribute(Qt::WA_TransparentForMouseEvents);
_quality.value(
) | rpl::start_with_next([=](int now) {
const auto chosen = (now == quality);
raw->action()->setEnabled(!chosen);
check->setVisible(chosen);
}, raw->lifetime());
menu->addAction(std::move(action));
};
add(0);
for (const auto quality : _qualities) {
add(quality);
}
}
} // namespace Media::Player

View File

@ -129,7 +129,10 @@ public:
not_null<QWidget*> menuParent,
Fn<void(bool)> menuOverCallback,
Fn<float64(bool lastNonDefault)> value,
Fn<void(float64)> change);
Fn<void(float64)> change,
std::vector<int> qualities = {},
Fn<int()> quality = nullptr,
Fn<void(int)> changeQuality = nullptr);
[[nodiscard]] rpl::producer<> saved() const;
@ -141,6 +144,7 @@ private:
[[nodiscard]] float64 lastNonDefaultSpeed() const;
void toggleDefault();
void setSpeed(float64 newSpeed);
void setQuality(int quality);
void save();
const style::MediaSpeedButton &_st;
@ -151,6 +155,11 @@ private:
rpl::event_stream<float64> _speedChanged;
rpl::event_stream<> _saved;
std::vector<int> _qualities;
Fn<int()> _lookupQuality;
Fn<void(int)> _changeQuality;
rpl::variable<int> _quality;
};
} // namespace Media::Player

View File

@ -17,6 +17,9 @@ inline constexpr auto kTimeUnknown = std::numeric_limits<crl::time>::min();
inline constexpr auto kDurationMax = crl::time(std::numeric_limits<int>::max());
inline constexpr auto kDurationUnavailable = std::numeric_limits<crl::time>::max();
inline constexpr auto kOriginalQuality = 0;
inline constexpr auto kAutoQuality = -1;
namespace Audio {
bool SupportsSpeedControl();
} // namespace Audio

View File

@ -307,17 +307,21 @@ mediaviewTitleMaximizeMacPadding: margins(0px, 4px, 8px, 4px);
mediaviewShadowTop: icon{{ "mediaview/shadow_top", windowShadowFg }};
mediaviewShadowBottom: icon{{ "mediaview/shadow_bottom", windowShadowFg }};
mediaviewSpeedMenuInner: Menu(mediaviewMenu) {
separator: MenuSeparator(mediaviewMenuSeparator) {
fg: groupCallMenuBgOver;
padding: margins(0px, 4px, 0px, 4px);
width: 6px;
}
itemPadding: margins(54px, 7px, 54px, 9px);
itemFgDisabled: mediaviewTextLinkFg;
}
mediaviewSpeedMenu: MediaSpeedMenu(mediaPlayerSpeedMenu) {
dropdown: DropdownMenu(mediaviewDropdownMenu) {
menu: Menu(mediaviewMenu) {
separator: MenuSeparator(mediaviewMenuSeparator) {
fg: groupCallMenuBgOver;
padding: margins(0px, 4px, 0px, 4px);
width: 6px;
}
itemPadding: margins(54px, 7px, 54px, 9px);
itemFgDisabled: mediaviewTextLinkFg;
}
menu: mediaviewSpeedMenuInner;
}
qualityMenu: Menu(mediaviewSpeedMenuInner) {
itemPadding: margins(17px, 7px, 54px, 9px);
}
activeCheck: icon {{ "player/player_check", mediaviewTextLinkFg }};
slider: MediaSlider(defaultContinuousSlider) {

View File

@ -1112,7 +1112,10 @@ bool OverlayWidget::videoShown() const {
QSize OverlayWidget::videoSize() const {
Expects(videoShown());
return flipSizeByRotation(_streamed->instance.info().video.size);
const auto use = (_document && _chosenQuality != _document)
? _document->dimensions
: _streamed->instance.info().video.size;
return flipSizeByRotation(use);
}
bool OverlayWidget::streamingRequiresControls() const {
@ -2257,16 +2260,37 @@ OverlayWidget::~OverlayWidget() {
_dropdown.destroy();
}
not_null<DocumentData*> OverlayWidget::chooseQuality() const {
Expects(_document != nullptr);
const auto video = _document->video();
if (!video || video->qualities.empty() || _quality == kOriginalQuality) {
return _document;
}
auto closest = _document;
auto closestAbs = std::abs(_quality - _document->resolveVideoQuality());
for (const auto &quality : video->qualities) {
const auto abs = std::abs(_quality - quality->resolveVideoQuality());
if (abs < closestAbs) {
closestAbs = abs;
closest = quality;
}
}
return closest;
}
void OverlayWidget::assignMediaPointer(DocumentData *document) {
_savePhotoVideoWhenLoaded = SavePhotoVideo::None;
_photo = nullptr;
_photoMedia = nullptr;
if (_document != document) {
if ((_document = document)) {
_chosenQuality = chooseQuality();
_documentMedia = _document->createMediaView();
_documentMedia->goodThumbnailWanted();
_documentMedia->thumbnailWanted(fileOrigin());
} else {
_chosenQuality = nullptr;
_documentMedia = nullptr;
}
_documentLoadingTo = QString();
@ -2275,6 +2299,7 @@ void OverlayWidget::assignMediaPointer(DocumentData *document) {
void OverlayWidget::assignMediaPointer(not_null<PhotoData*> photo) {
_savePhotoVideoWhenLoaded = SavePhotoVideo::None;
_chosenQuality = nullptr;
_document = nullptr;
_documentMedia = nullptr;
_documentLoadingTo = QString();
@ -3848,12 +3873,12 @@ void OverlayWidget::startStreamingPlayer(
return;
}
const auto position = _document
_streamedPosition = _document
? startStreaming.startTime
: _photo
? _photo->videoStartPosition()
: 0;
restartAtSeekPosition(position);
restartAtSeekPosition(_streamedPosition);
}
void OverlayWidget::initStreamingThumbnail() {
@ -3892,9 +3917,15 @@ void OverlayWidget::initStreamingThumbnail() {
: good
? good->size()
: _document->dimensions;
if (!good && !thumbnail && !blurred) {
if (size.isEmpty()) {
return;
} else if (size.isEmpty()) {
} else if (!_streamedQualityChangeFrame.isNull()) {
setStaticContent(_streamedQualityChangeFrame.scaled(
size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
return;
} else if (!good && !thumbnail && !blurred) {
return;
}
const auto options = VideoThumbOptions(_document);
@ -3917,6 +3948,7 @@ void OverlayWidget::initStreamingThumbnail() {
void OverlayWidget::streamingReady(Streaming::Information &&info) {
if (videoShown()) {
applyVideoSize();
_streamedQualityChangeFrame = QImage();
} else {
updateContentRect();
}
@ -3938,8 +3970,9 @@ bool OverlayWidget::createStreamingObjects() {
const auto origin = fileOrigin();
const auto callback = [=] { waitingAnimationCallback(); };
if (_document) {
_streamed = std::make_unique<Streamed>(_document, origin, callback);
const auto video = _chosenQuality ? _chosenQuality : _document;
if (video) {
_streamed = std::make_unique<Streamed>(video, origin, callback);
} else {
_streamed = std::make_unique<Streamed>(_photo, origin, callback);
}
@ -3950,8 +3983,8 @@ bool OverlayWidget::createStreamingObjects() {
++_streamedCreated;
_streamed->instance.setPriority(kOverlayLoaderPriority);
_streamed->instance.lockPlayer();
_streamed->withSound = _document
&& !_document->isSilentVideo()
_streamed->withSound = video
&& !video->isSilentVideo()
&& (_document->isAudioFile()
|| _document->isVideoFile()
|| _document->isVoiceMessage()
@ -4018,6 +4051,7 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) {
updateContentRect();
Core::App().updateNonIdle();
updatePlaybackState();
_streamedPosition = update.position;
}, [&](const PreloadedAudio &update) {
updatePlaybackState();
}, [&](const UpdateAudio &update) {
@ -4381,6 +4415,45 @@ float64 OverlayWidget::playbackControlsCurrentSpeed(bool lastNonDefault) {
return Core::App().settings().videoPlaybackSpeed(lastNonDefault);
}
std::vector<int> OverlayWidget::playbackControlsQualities() {
const auto video = _document ? _document->video() : nullptr;
if (!video || video->qualities.empty()) {
return {};
}
auto result = std::vector<int>();
result.reserve(video->qualities.size());
for (const auto &quality : video->qualities) {
result.push_back(quality->resolveVideoQuality());
}
return result;
}
int OverlayWidget::playbackControlsCurrentQuality() {
return _quality;
}
void OverlayWidget::playbackControlsQualityChanged(int quality) {
const auto now = _chosenQuality;
if (_quality != quality) {
_quality = quality;
if (_document) {
_chosenQuality = chooseQuality();
if (_chosenQuality != now) {
if (_streamed && _streamed->instance.ready()) {
_streamedQualityChangeFrame = currentVideoFrameImage();
}
clearStreaming();
_streamingStartPaused = false;
const auto time = _streamedPosition;
const auto startStreaming = StartStreaming(false, time);
if (!canInitStreaming() || !initStreaming(startStreaming)) {
redisplayContent();
}
}
}
}
}
void OverlayWidget::switchToPip() {
Expects(_streamed != nullptr);
Expects(_document != nullptr);
@ -4628,6 +4701,7 @@ void OverlayWidget::updatePlaybackState() {
}
const auto state = _streamed->instance.player().prepareLegacyState();
if (state.position != kTimeUnknown && state.length != kTimeUnknown) {
_streamedPosition = state.position;
if (_streamed->controls) {
_streamed->controls->updatePlayback(state);
_touchbarTrackState.fire_copy(state);

View File

@ -236,6 +236,9 @@ private:
void playbackControlsVolumeChangeFinished() override;
void playbackControlsSpeedChanged(float64 speed) override;
float64 playbackControlsCurrentSpeed(bool lastNonDefault) override;
std::vector<int> playbackControlsQualities() override;
int playbackControlsCurrentQuality() override;
void playbackControlsQualityChanged(int quality) override;
void playbackControlsToFullScreen() override;
void playbackControlsFromFullScreen() override;
void playbackControlsToPictureInPicture() override;
@ -315,11 +318,11 @@ private:
void checkForSaveLoaded();
void showPremiumDownloadPromo();
Entity entityForUserPhotos(int index) const;
Entity entityForSharedMedia(int index) const;
Entity entityForCollage(int index) const;
Entity entityByIndex(int index) const;
Entity entityForItemId(const FullMsgId &itemId) const;
[[nodiscard]] Entity entityForUserPhotos(int index) const;
[[nodiscard]] Entity entityForSharedMedia(int index) const;
[[nodiscard]] Entity entityForCollage(int index) const;
[[nodiscard]] Entity entityByIndex(int index) const;
[[nodiscard]] Entity entityForItemId(const FullMsgId &itemId) const;
bool moveToEntity(const Entity &entity, int preloadDelta = 0);
void setContext(std::variant<
@ -335,23 +338,23 @@ private:
struct SharedMedia;
using SharedMediaType = SharedMediaWithLastSlice::Type;
using SharedMediaKey = SharedMediaWithLastSlice::Key;
std::optional<SharedMediaType> sharedMediaType() const;
std::optional<SharedMediaKey> sharedMediaKey() const;
std::optional<SharedMediaType> computeOverviewType() const;
[[nodiscard]] std::optional<SharedMediaType> sharedMediaType() const;
[[nodiscard]] std::optional<SharedMediaKey> sharedMediaKey() const;
[[nodiscard]] std::optional<SharedMediaType> computeOverviewType() const;
bool validSharedMedia() const;
void validateSharedMedia();
void handleSharedMediaUpdate(SharedMediaWithLastSlice &&update);
struct UserPhotos;
using UserPhotosKey = UserPhotosSlice::Key;
std::optional<UserPhotosKey> userPhotosKey() const;
[[nodiscard]] std::optional<UserPhotosKey> userPhotosKey() const;
bool validUserPhotos() const;
void validateUserPhotos();
void handleUserPhotosUpdate(UserPhotosSlice &&update);
struct Collage;
using CollageKey = WebPageCollage::Item;
std::optional<CollageKey> collageKey() const;
[[nodiscard]] std::optional<CollageKey> collageKey() const;
bool validCollage() const;
void validateCollage();
@ -430,11 +433,11 @@ private:
void contentSizeChanged();
// Radial animation interface.
float64 radialProgress() const;
bool radialLoading() const;
QRect radialRect() const;
[[nodiscard]] float64 radialProgress() const;
[[nodiscard]] bool radialLoading() const;
[[nodiscard]] QRect radialRect() const;
void radialStart();
crl::time radialTimeShift() const;
[[nodiscard]] crl::time radialTimeShift() const;
void updateHeader();
void snapXY();
@ -524,6 +527,7 @@ private:
void clearStreaming(bool savePosition = true);
[[nodiscard]] bool canInitStreaming() const;
[[nodiscard]] bool saveControlLocked() const;
[[nodiscard]] not_null<DocumentData*> chooseQuality() const;
[[nodiscard]] bool topShadowOnTheRight() const;
void applyHideWindowWorkaround();
@ -551,6 +555,8 @@ private:
rpl::lifetime _sessionLifetime;
PhotoData *_photo = nullptr;
DocumentData *_document = nullptr;
DocumentData *_chosenQuality = nullptr;
int _quality = {};
QString _documentLoadingTo;
std::shared_ptr<Data::PhotoMedia> _photoMedia;
std::shared_ptr<Data::DocumentMedia> _documentMedia;
@ -625,6 +631,8 @@ private:
std::unique_ptr<Streamed> _streamed;
std::unique_ptr<PipWrap> _pip;
QImage _streamedQualityChangeFrame;
crl::time _streamedPosition = 0;
int _streamedCreated = 0;
bool _showAsPip = false;

View File

@ -47,7 +47,10 @@ PlaybackControls::PlaybackControls(
parent,
[=](bool) {},
[=](bool lastNonDefault) { return speedLookup(lastNonDefault); },
[=](float64 speed) { saveSpeed(speed); })
[=](float64 speed) { saveSpeed(speed); },
_delegate->playbackControlsQualities(),
[=] { return _delegate->playbackControlsCurrentQuality(); },
[=](int quality) { saveQuality(quality); })
: nullptr)
, _fadeAnimation(std::make_unique<Ui::FadeAnimation>(this)) {
_fadeAnimation->show();
@ -192,6 +195,10 @@ void PlaybackControls::saveSpeed(float64 speed) {
_delegate->playbackControlsSpeedChanged(speed);
}
void PlaybackControls::saveQuality(int quality) {
_delegate->playbackControlsQualityChanged(quality);
}
void PlaybackControls::updatePlaybackSpeed(float64 speed) {
DEBUG_LOG(("Media playback speed: update to %1.").arg(speed));
_delegate->playbackControlsSpeedChanged(speed);

View File

@ -44,6 +44,10 @@ public:
virtual void playbackControlsSpeedChanged(float64 speed) = 0;
[[nodiscard]] virtual float64 playbackControlsCurrentSpeed(
bool lastNonDefault) = 0;
[[nodiscard]] virtual auto playbackControlsQualities()
-> std::vector<int> = 0;
[[nodiscard]] virtual int playbackControlsCurrentQuality() = 0;
virtual void playbackControlsQualityChanged(int quality) = 0;
virtual void playbackControlsToFullScreen() = 0;
virtual void playbackControlsFromFullScreen() = 0;
virtual void playbackControlsToPictureInPicture() = 0;
@ -90,6 +94,8 @@ private:
[[nodiscard]] float64 speedLookup(bool lastNonDefault) const;
void saveSpeed(float64 speed);
void saveQuality(int quality);
const not_null<Delegate*> _delegate;
bool _inFullScreen = false;