Reuse new speed change control for video.

This commit is contained in:
John Preston 2023-03-15 13:36:32 +04:00
parent 4351baffb3
commit 1eff68813d
57 changed files with 875 additions and 979 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 B

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 695 B

After

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 B

After

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 919 B

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 594 B

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 879 B

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 566 B

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 B

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 B

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 B

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 B

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 877 B

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 478 B

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 646 B

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 B

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 668 B

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 962 B

After

Width:  |  Height:  |  Size: 858 B

View file

@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/platform/base_platform_info.h"
#include "webrtc/webrtc_create_adm.h"
#include "media/player/media_player_instance.h"
#include "media/audio/media_audio.h"
#include "media/media_common.h"
#include "ui/gl/gl_detection.h"
#include "calls/group/calls_group_common.h"
#include "spellcheck/spellcheck_types.h"
@ -119,10 +119,6 @@ void LogPosition(const WindowPosition &position, const QString &name) {
return position;
}
float64 Settings::PlaybackSpeed::Default() {
return Media::Audio::kSpedUpDefault;
}
Settings::Settings()
: _sendSubmitWay(Ui::InputSubmitSettings::Enter)
, _floatPlayerColumn(Window::Column::Second)
@ -814,17 +810,17 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_closeToTaskbar = (closeToTaskbar == 1);
_customDeviceModel = customDeviceModel;
_accountsOrder = accountsOrder;
const auto uncheckedPlayerRepeatMode = static_cast<Media::Player::RepeatMode>(playerRepeatMode);
const auto uncheckedPlayerRepeatMode = static_cast<Media::RepeatMode>(playerRepeatMode);
switch (uncheckedPlayerRepeatMode) {
case Media::Player::RepeatMode::None:
case Media::Player::RepeatMode::One:
case Media::Player::RepeatMode::All: _playerRepeatMode = uncheckedPlayerRepeatMode; break;
case Media::RepeatMode::None:
case Media::RepeatMode::One:
case Media::RepeatMode::All: _playerRepeatMode = uncheckedPlayerRepeatMode; break;
}
const auto uncheckedPlayerOrderMode = static_cast<Media::Player::OrderMode>(playerOrderMode);
const auto uncheckedPlayerOrderMode = static_cast<Media::OrderMode>(playerOrderMode);
switch (uncheckedPlayerOrderMode) {
case Media::Player::OrderMode::Default:
case Media::Player::OrderMode::Reverse:
case Media::Player::OrderMode::Shuffle: _playerOrderMode = uncheckedPlayerOrderMode; break;
case Media::OrderMode::Default:
case Media::OrderMode::Reverse:
case Media::OrderMode::Shuffle: _playerOrderMode = uncheckedPlayerOrderMode; break;
}
_macWarnBeforeQuit = (macWarnBeforeQuit == 1);
_hardwareAcceleratedVideo = (hardwareAcceleratedVideo == 1);
@ -1178,7 +1174,7 @@ float64 Settings::DefaultDialogsWidthRatio() {
}
qint32 Settings::SerializePlaybackSpeed(PlaybackSpeed speed) {
using namespace Media::Audio;
using namespace Media;
const auto value = int(base::SafeRound(
std::clamp(speed.value, kSpeedMin, kSpeedMax) * 100));
@ -1186,7 +1182,7 @@ qint32 Settings::SerializePlaybackSpeed(PlaybackSpeed speed) {
}
auto Settings::DeserializePlaybackSpeed(qint32 speed) -> PlaybackSpeed {
using namespace Media::Audio;
using namespace Media;
auto enabled = true;
const auto validate = [&](float64 result) {

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "core/core_settings_proxy.h"
#include "media/media_common.h"
#include "window/themes/window_themes_embedded.h"
#include "ui/chat/attach/attach_send_files_way.h"
#include "platform/platform_notifications_manager.h"
@ -37,11 +38,6 @@ namespace Calls::Group {
enum class StickedTooltip;
} // namespace Calls::Group
namespace Media::Player {
enum class RepeatMode;
enum class OrderMode;
} // namespace Media::Player
namespace Core {
struct WindowPosition {
@ -718,28 +714,28 @@ public:
[[nodiscard]] rpl::producer<QString> deviceModelChanges() const;
[[nodiscard]] rpl::producer<QString> deviceModelValue() const;
void setPlayerRepeatMode(Media::Player::RepeatMode mode) {
void setPlayerRepeatMode(Media::RepeatMode mode) {
_playerRepeatMode = mode;
}
[[nodiscard]] Media::Player::RepeatMode playerRepeatMode() const {
[[nodiscard]] Media::RepeatMode playerRepeatMode() const {
return _playerRepeatMode.current();
}
[[nodiscard]] rpl::producer<Media::Player::RepeatMode> playerRepeatModeValue() const {
[[nodiscard]] rpl::producer<Media::RepeatMode> playerRepeatModeValue() const {
return _playerRepeatMode.value();
}
[[nodiscard]] rpl::producer<Media::Player::RepeatMode> playerRepeatModeChanges() const {
[[nodiscard]] rpl::producer<Media::RepeatMode> playerRepeatModeChanges() const {
return _playerRepeatMode.changes();
}
void setPlayerOrderMode(Media::Player::OrderMode mode) {
void setPlayerOrderMode(Media::OrderMode mode) {
_playerOrderMode = mode;
}
[[nodiscard]] Media::Player::OrderMode playerOrderMode() const {
[[nodiscard]] Media::OrderMode playerOrderMode() const {
return _playerOrderMode.current();
}
[[nodiscard]] rpl::producer<Media::Player::OrderMode> playerOrderModeValue() const {
[[nodiscard]] rpl::producer<Media::OrderMode> playerOrderModeValue() const {
return _playerOrderMode.value();
}
[[nodiscard]] rpl::producer<Media::Player::OrderMode> playerOrderModeChanges() const {
[[nodiscard]] rpl::producer<Media::OrderMode> playerOrderModeChanges() const {
return _playerOrderMode.changes();
}
[[nodiscard]] std::vector<uint64> accountsOrder() const {
@ -811,9 +807,7 @@ public:
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
struct PlaybackSpeed {
[[nodiscard]] static float64 Default();
float64 value = Default();
float64 value = Media::kSpedUpDefault;
bool enabled = false;
};
[[nodiscard]] static qint32 SerializePlaybackSpeed(PlaybackSpeed speed);
@ -909,8 +903,8 @@ private:
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
rpl::variable<bool> _closeToTaskbar = false;
rpl::variable<QString> _customDeviceModel;
rpl::variable<Media::Player::RepeatMode> _playerRepeatMode;
rpl::variable<Media::Player::OrderMode> _playerOrderMode;
rpl::variable<Media::RepeatMode> _playerRepeatMode;
rpl::variable<Media::OrderMode> _playerOrderMode;
bool _macWarnBeforeQuit = true;
std::vector<uint64> _accountsOrder;
#ifdef Q_OS_MAC

View file

@ -491,7 +491,7 @@ void Mixer::Track::updateWithSpeedPosition() {
int64 Mixer::Track::SpeedIndependentPosition(
int64 position,
float64 speed) {
Expects(speed <= Audio::kSpeedMax);
Expects(speed <= kSpeedMax);
return int64(base::SafeRound(position * speed));
}
@ -499,7 +499,7 @@ int64 Mixer::Track::SpeedIndependentPosition(
int64 Mixer::Track::SpeedDependentPosition(
int64 position,
float64 speed) {
Expects(speed >= Audio::kSpeedMin);
Expects(speed >= kSpeedMin);
return int64(base::SafeRound(position / speed));
}

View file

@ -35,10 +35,6 @@ namespace Audio {
class Instance;
inline constexpr auto kSpeedMin = 0.5;
inline constexpr auto kSpeedMax = 2.5;
inline constexpr auto kSpedUpDefault = 1.7;
// Thread: Main.
void Start(not_null<Instance*> instance);
void Finish(not_null<Instance*> instance);

View file

@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/audio/media_audio_ffmpeg_loader.h"
#include "base/bytes.h"
#include "core/file_location.h"
#include "ffmpeg/ffmpeg_utility.h"
#include "base/bytes.h"
#include "media/media_common.h"
extern "C" {
#include <libavfilter/buffersink.h>
@ -540,7 +541,7 @@ bool AbstractAudioFFMpegLoader::ensureResampleSpaceAvailable(int samples) {
}
bool AbstractAudioFFMpegLoader::changeSpeedFilter(float64 speed) {
speed = std::clamp(speed, Audio::kSpeedMin, Audio::kSpeedMax);
speed = std::clamp(speed, kSpeedMin, kSpeedMax);
if (_filterSpeed == speed) {
return false;
}

View file

@ -0,0 +1,28 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Media {
enum class RepeatMode {
None,
One,
All,
};
enum class OrderMode {
Default,
Reverse,
Shuffle,
};
inline constexpr auto kSpeedMin = 0.5;
inline constexpr auto kSpeedMax = 2.5;
inline constexpr auto kSpedUpDefault = 1.7;
} // namespace Media

View file

@ -27,31 +27,42 @@ MediaPlayerButton {
duration: int;
}
MediaSpeedButton {
width: pixels;
height: pixels;
font: font;
icon: icon;
}
MediaSpeedMenu {
menu: Menu;
iconFg: color;
iconFgActive: color;
textFgActive: color;
dropdown: DropdownMenu;
activeCheck: icon;
activeCheckSkip: pixels;
sliderStyle: TextStyle;
sliderPadding: margins;
sliderWidth: pixels;
slider: MediaSlider;
slow: icon;
slowActive: icon;
normal: icon;
normalActive: icon;
medium: icon;
mediumActive: icon;
fast: icon;
fastActive: icon;
veryFast: icon;
veryFastActive: icon;
superFast: icon;
superFastActive: icon;
}
mediaSpeedButton: MediaSpeedButton {
width: 24px;
height: 24px;
font: font(11px semibold);
icon: icon{{ "player/player_speed", menuIconFg }};
MediaSpeedButton {
size: size;
padding: margins;
font: font;
fg: color;
overFg: color;
activeFg: color;
icon: icon;
ripple: RippleAnimation;
rippleActiveColor: color;
rippleRadius: pixels;
menu: MediaSpeedMenu;
menuAlign: align;
}
mediaPlayerButton: MediaPlayerButton {
@ -142,13 +153,6 @@ mediaPlayerCancelIcon: icon{
{ "player/panel_close", mediaPlayerActiveFg }
};
mediaPlayerSpeedSize: size(30px, 30px);
mediaPlayerSpeedRadius: 4px;
mediaPlayerSpeedRipple: RippleAnimation(defaultRippleAnimation) {
color: lightButtonBgOver;
}
mediaPlayerSpeedDisabledRippleBg: windowBgOver;
mediaPlayerMenu: DropdownMenu(defaultDropdownMenu) {
wrap: InnerDropdown(defaultInnerDropdown) {
scrollPadding: margins(0px, 4px, 0px, 4px);
@ -157,18 +161,17 @@ mediaPlayerMenu: DropdownMenu(defaultDropdownMenu) {
}
mediaPlayerMenuCheck: icon {{ "player/player_check", mediaPlayerActiveFg }};
mediaSpeedMenu: MediaSpeedMenu {
menu: Menu(menuWithIcons) {
separator: MenuSeparator(defaultMenuSeparator) {
padding: margins(0px, 4px, 0px, 4px);
width: 6px;
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;
}
itemPadding: margins(54px, 7px, 54px, 9px);
itemFgDisabled: mediaPlayerActiveFg;
}
iconFg: menuIconColor;
iconFgActive: mediaPlayerActiveFg;
textFgActive: mediaPlayerActiveFg;
activeCheck: mediaPlayerMenuCheck;
activeCheckSkip: 8px;
sliderStyle: TextStyle(defaultTextStyle) {
@ -186,19 +189,37 @@ mediaSpeedMenu: MediaSpeedMenu {
width: 6px;
seekSize: size(6px, 6px);
}
slow: playerSpeedSlow;
slowActive: playerSpeedSlowActive;
normal: playerSpeedNormal;
normalActive: playerSpeedNormalActive;
medium: playerSpeedMedium;
mediumActive: playerSpeedMediumActive;
fast: playerSpeedFast;
fastActive: playerSpeedFastActive;
veryFast: playerSpeedVeryFast;
veryFastActive: playerSpeedVeryFastActive;
superFast: playerSpeedSuperFast;
superFastActive: playerSpeedSuperFastActive;
}
mediaPlayerSpeedButton: MediaSpeedButton {
size: size(30px, 30px);
padding: margins(0px, 6px, 0px, 0px);
font: font(11px bold);
fg: menuIconFg;
overFg: menuIconFgOver;
activeFg: mediaPlayerActiveFg;
icon: icon{{ "player/player_speed", menuIconFg }};
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
}
rippleActiveColor: lightButtonBgOver;
rippleRadius: 4px;
menu: mediaPlayerSpeedMenu;
menuAlign: align(topright);
}
mediaSpeedSlow: icon {{ "player/speed/audiospeed_menu_0.5", menuIconColor }};
mediaSpeedSlowActive: icon {{ "player/speed/audiospeed_menu_0.5", mediaPlayerActiveFg }};
mediaSpeedNormal: icon {{ "player/speed/audiospeed_menu_1.0", menuIconColor }};
mediaSpeedNormalActive: icon {{ "player/speed/audiospeed_menu_1.0", mediaPlayerActiveFg }};
mediaSpeedMedium: icon {{ "player/speed/audiospeed_menu_1.2", menuIconColor }};
mediaSpeedMediumActive: icon {{ "player/speed/audiospeed_menu_1.2", mediaPlayerActiveFg }};
mediaSpeedFast: icon {{ "player/speed/audiospeed_menu_1.5", menuIconColor }};
mediaSpeedFastActive: icon {{ "player/speed/audiospeed_menu_1.5", mediaPlayerActiveFg }};
mediaSpeedVeryFast: icon {{ "player/speed/audiospeed_menu_1.7", menuIconColor }};
mediaSpeedVeryFastActive: icon {{ "player/speed/audiospeed_menu_1.7", mediaPlayerActiveFg }};
mediaSpeedSuperFast: icon {{ "player/speed/audiospeed_menu_2.0", menuIconColor }};
mediaSpeedSuperFastActive: icon {{ "player/speed/audiospeed_menu_2.0", mediaPlayerActiveFg }};
mediaPlayerVolumeIcon0: icon {
{ "player/player_mini_off", mediaPlayerActiveFg },
@ -292,7 +313,7 @@ mediaPlayerFileLayout: OverviewFileLayout(overviewFileLayout) {
mediaPlayerFloatSize: 128px;
mediaPlayerFloatMargin: 12px;
mediaPlayerMenuPosition: point(-2px, -2px);
mediaPlayerMenuPosition: point(-2px, -1px);
mediaPlayerOrderMenu: Menu(defaultMenu) {
itemIconPosition: point(13px, 8px);
itemPadding: margins(49px, 9px, 17px, 11px);

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/player/media_player_button.h"
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
#include "styles/style_media_player.h"
@ -263,82 +264,73 @@ SpeedButtonLayout::SpeedButtonLayout(
float64 speed)
: _st(st)
, _speed(speed)
, _oldSpeed(speed)
, _nextSpeed(speed)
, _metrics(_st.font->f)
, _text(SpeedText(speed))
, _textWidth(_metrics.horizontalAdvance(_text))
, _oldText(_text)
, _oldTextWidth(_textWidth)
, _callback(std::move(callback)) {
}
void SpeedButtonLayout::setSpeed(float64 speed) {
speed = base::SafeRound(speed * 10.) / 10.;
if (_nextSpeed == speed) {
return;
}
_nextSpeed = speed;
if (!_transformProgress.animating()) {
_oldSpeed = _speed;
_oldColor = _lastPaintColor;
_oldText = _text;
_oldTextWidth = _textWidth;
_speed = _nextSpeed;
if (_speed != speed) {
_speed = speed;
_text = SpeedText(_speed);
_textWidth = _metrics.horizontalAdvance(_text);
_transformBackward = false;
if (_speed != _speed) {
startTransform(0., 1.);
if (_callback) _callback();
}
} else if (_oldSpeed == _nextSpeed) {
std::swap(_oldSpeed, _speed);
std::swap(_oldColor, _lastPaintColor);
std::swap(_oldText, _text);
std::swap(_oldTextWidth, _textWidth);
startTransform(
_transformBackward ? 0. : 1.,
_transformBackward ? 1. : 0.);
_transformBackward = !_transformBackward;
if (_callback) _callback();
}
}
void SpeedButtonLayout::finishTransform() {
_transformProgress.stop();
_transformBackward = false;
if (_callback) _callback();
}
void SpeedButtonLayout::paint(QPainter &p, const QColor &color) {
_lastPaintColor = color;
_st.icon.paint(p, 0, 0, _st.width, color);
void SpeedButtonLayout::paint(QPainter &p, bool over, bool active) {
const auto &color = active ? _st.activeFg : over ? _st.overFg : _st.fg;
const auto inner = QRect(QPoint(), _st.size).marginsRemoved(_st.padding);
_st.icon.paintInCenter(p, inner, color->c);
p.setPen(color);
p.setFont(_st.font);
p.drawText(
QPointF(
(_st.width - _textWidth) / 2.,
(_st.height - _metrics.height()) / 2. + _metrics.ascent()),
QPointF(inner.topLeft()) + QPointF(
(inner.width() - _textWidth) / 2.,
(inner.height() - _metrics.height()) / 2. + _metrics.ascent()),
_text);
}
void SpeedButtonLayout::animationCallback() {
if (!_transformProgress.animating()) {
const auto finalSpeed = _nextSpeed;
_nextSpeed = _speed;
setSpeed(finalSpeed);
}
_callback();
SpeedButton::SpeedButton(QWidget *parent, const style::MediaSpeedButton &st)
: RippleButton(parent, st.ripple)
, _st(st)
, _layout(st, [=] { update(); }, 2.)
, _isDefault(true) {
resize(_st.size);
}
void SpeedButtonLayout::startTransform(float64 from, float64 to) {
// No animation for now.
_transformProgress.stop();
animationCallback();
void SpeedButton::setSpeed(float64 speed, anim::type animated) {
_isDefault = (speed == 1.);
_layout.setSpeed(speed);
update();
}
void SpeedButton::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
paintRipple(
p,
QPoint(_st.padding.left(), _st.padding.top()),
_isDefault ? nullptr : &_st.rippleActiveColor->c);
_layout.paint(p, isOver(), !_isDefault);
}
QPoint SpeedButton::prepareRippleStartPosition() const {
const auto inner = rect().marginsRemoved(_st.padding);
const auto result = mapFromGlobal(QCursor::pos()) - inner.topLeft();
return inner.contains(result)
? result
: DisabledRippleStartPosition();
}
QImage SpeedButton::prepareRippleMask() const {
return Ui::RippleAnimation::RoundRectMask(
rect().marginsRemoved(_st.padding).size(),
_st.rippleRadius);
}
} // namespace Media::Player

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "ui/effects/animations.h"
#include "ui/widgets/buttons.h"
#include <QtGui/QFontMetrics>
@ -60,32 +61,42 @@ public:
float64 speed);
void setSpeed(float64 speed);
void finishTransform();
void paint(QPainter &p, const QColor &color);
void paint(QPainter &p, bool over, bool active);
private:
void animationCallback();
void startTransform(float64 from, float64 to);
const style::MediaSpeedButton &_st;
float64 _speed = 1.;
float64 _oldSpeed = 1.;
float64 _nextSpeed = 1.;
std::optional<QColor> _lastPaintColor;
std::optional<QColor> _oldColor;
Ui::Animations::Simple _transformProgress;
bool _transformBackward = false;
QFontMetricsF _metrics;
QString _text;
float64 _textWidth = 0;
QString _oldText;
float64 _oldTextWidth = 0;
Fn<void()> _callback;
};
class SpeedButton final : public Ui::RippleButton {
public:
SpeedButton(QWidget *parent, const style::MediaSpeedButton &st);
[[nodiscard]] const style::MediaSpeedButton &st() const {
return _st;
}
void setSpeed(float64 speed, anim::type animated = anim::type::normal);
private:
void paintEvent(QPaintEvent *e) override;
QPoint prepareRippleStartPosition() const override;
QImage prepareRippleMask() const override;
const style::MediaSpeedButton &_st;
SpeedButtonLayout _layout;
bool _isDefault = false;
};
} // namespace Media::Player

View file

@ -7,24 +7,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/player/media_player_dropdown.h"
#include "base/invoke_queued.h"
#include "base/timer.h"
#include "lang/lang_keys.h"
#include "media/player/media_player_button.h"
#include "ui/cached_round_corners.h"
#include "ui/widgets/menu/menu.h"
#include "ui/widgets/menu/menu_action.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/shadow.h"
#include "ui/painter.h"
#include "styles/style_media_player.h"
#include "styles/style_widgets.h"
#include "base/debug_log.h"
namespace Media::Player {
namespace {
constexpr auto kSpeedMin = 0.5;
constexpr auto kSpeedMax = 2.5;
constexpr auto kSpeedDebounceTimeout = crl::time(1000);
[[nodiscard]] float64 SpeedToSliderValue(float64 speed) {
@ -87,12 +86,12 @@ SpeedSliderItem::SpeedSliderItem(
not_null<RpWidget*> parent,
const style::MediaSpeedMenu &st,
rpl::producer<float64> value)
: Ui::Menu::ItemBase(parent, st.menu)
: Ui::Menu::ItemBase(parent, st.dropdown.menu)
, _slider(base::make_unique_q<Ui::MediaSlider>(this, st.slider))
, _dummyAction(new QAction(parent))
, _st(st)
, _height(st.sliderPadding.top()
+ st.menu.itemStyle.font->height
+ st.dropdown.menu.itemStyle.font->height
+ st.sliderPadding.bottom())
, _debounceTimer([=] { _debounced.fire(current()); }) {
initResizeHook(parent->sizeValue());
@ -120,11 +119,11 @@ SpeedSliderItem::SpeedSliderItem(
) | rpl::start_with_next([=](const QRect &clip) {
auto p = Painter(this);
p.fillRect(clip, _st.menu.itemBg);
p.fillRect(clip, _st.dropdown.menu.itemBg);
const auto left = (_st.sliderPadding.left() - _text.maxWidth()) / 2;
const auto top = _st.menu.itemPadding.top();
p.setPen(_st.menu.itemFg);
const auto top = _st.dropdown.menu.itemPadding.top();
p.setPen(_st.dropdown.menu.itemFg);
_text.drawLeftElided(p, left, top, _text.maxWidth(), width());
}, lifetime());
@ -172,6 +171,111 @@ SpeedSliderItem::SpeedSliderItem(
});
}
void FillSpeedMenu(
not_null<Ui::Menu::Menu*> menu,
const style::MediaSpeedMenu &st,
rpl::producer<float64> value,
Fn<void(float64)> callback) {
auto slider = base::make_unique_q<SpeedSliderItem>(
menu,
st,
rpl::duplicate(value));
slider->debouncedChanges(
) | rpl::start_with_next(callback, slider->lifetime());
struct State {
rpl::variable<float64> realtime;
};
const auto state = slider->lifetime().make_state<State>();
state->realtime = rpl::single(
slider->current()
) | rpl::then(rpl::merge(
slider->changing(),
slider->changed()
));
menu->addAction(std::move(slider));
menu->addSeparator(&st.dropdown.menu.separator);
struct SpeedPoint {
float64 speed = 0.;
tr::phrase<> text;
const style::icon &icon;
const style::icon &iconActive;
};
const auto points = std::vector<SpeedPoint>{
{
0.5,
tr::lng_voice_speed_slow,
st.slow,
st.slowActive },
{
1.0,
tr::lng_voice_speed_normal,
st.normal,
st.normalActive },
{
1.2,
tr::lng_voice_speed_medium,
st.medium,
st.mediumActive },
{
1.5,
tr::lng_voice_speed_fast,
st.fast,
st.fastActive },
{
1.7,
tr::lng_voice_speed_very_fast,
st.veryFast,
st.veryFastActive },
{
2.0,
tr::lng_voice_speed_super_fast,
st.superFast,
st.superFastActive },
};
for (const auto &point : points) {
const auto speed = point.speed;
const auto text = point.text(tr::now);
const auto icon = &point.icon;
const auto iconActive = &point.iconActive;
auto action = base::make_unique_q<Ui::Menu::Action>(
menu,
st.dropdown.menu,
Ui::Menu::CreateAction(menu, text, [=] { callback(speed); }),
&point.icon,
&point.icon);
const auto raw = action.get();
const auto check = Ui::CreateChild<Ui::RpWidget>(raw);
const auto skip = st.activeCheckSkip;
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);
state->realtime.value(
) | rpl::start_with_next([=](float64 now) {
const auto chosen = (speed == now);
const auto overriden = chosen ? iconActive : icon;
raw->setIcon(overriden, overriden);
raw->action()->setEnabled(!chosen);
check->setVisible(chosen);
}, raw->lifetime());
menu->addAction(std::move(action));
}
}
void SpeedSliderItem::setExternalValue(float64 speed) {
if (!_slider->isChanging()) {
setSliderValue(speed);
@ -379,109 +483,284 @@ bool Dropdown::eventFilter(QObject *obj, QEvent *e) {
return false;
}
void FillSpeedMenu(
not_null<Ui::Menu::Menu*> menu,
const style::MediaSpeedMenu &st,
rpl::producer<float64> value,
Fn<void(float64)> callback) {
auto slider = base::make_unique_q<SpeedSliderItem>(
menu,
st,
rpl::duplicate(value));
WithDropdownController::WithDropdownController(
not_null<Ui::AbstractButton*> button,
not_null<QWidget*> menuParent,
const style::DropdownMenu &menuSt,
Qt::Alignment menuAlign,
Fn<void(bool)> menuOverCallback)
: _button(button)
, _menuParent(menuParent)
, _menuSt(menuSt)
, _menuAlign(menuAlign)
, _menuOverCallback(std::move(menuOverCallback)) {
button->events(
) | rpl::filter([=](not_null<QEvent*> e) {
return (e->type() == QEvent::Enter)
|| (e->type() == QEvent::Leave);
}) | rpl::start_with_next([=](not_null<QEvent*> e) {
_overButton = (e->type() == QEvent::Enter);
if (_overButton) {
InvokeQueued(button, [=] {
if (_overButton) {
showMenu();
}
});
}
}, button->lifetime());
}
slider->debouncedChanges(
) | rpl::start_with_next(callback, slider->lifetime());
not_null<Ui::AbstractButton*> WithDropdownController::button() const {
return _button;
}
struct State {
rpl::variable<float64> realtime;
};
const auto state = slider->lifetime().make_state<State>();
state->realtime = rpl::single(
slider->current()
) | rpl::then(rpl::merge(
slider->changing(),
slider->changed()
));
Ui::DropdownMenu *WithDropdownController::menu() const {
return _menu.get();
}
menu->addAction(std::move(slider));
menu->addSeparator(&st.menu.separator);
void WithDropdownController::updateDropdownGeometry() {
if (!_menu) {
return;
}
const auto bwidth = _button->width();
const auto bheight = _button->height();
const auto mwidth = _menu->width();
const auto mheight = _menu->height();
const auto padding = _menuSt.wrap.padding;
const auto x = st::mediaPlayerMenuPosition.x();
const auto y = st::mediaPlayerMenuPosition.y();
const auto position = _menu->parentWidget()->mapFromGlobal(
_button->mapToGlobal(QPoint())
) + [&] {
switch (_menuAlign) {
case style::al_topleft: return QPoint(
-padding.left() - x,
bheight - padding.top() + y);
case style::al_topright: return QPoint(
bwidth - mwidth + padding.right() + x,
bheight - padding.top() + y);
case style::al_bottomright: return QPoint(
bwidth - mwidth + padding.right() + x,
-mheight + padding.bottom() - y);
case style::al_bottomleft: return QPoint(
-padding.left() - x,
-mheight + padding.bottom() - y);
}
Unexpected("Menu align value.");
}();
_menu->move(position);
}
struct SpeedPoint {
float64 speed = 0.;
tr::phrase<> text;
const style::icon &icon;
const style::icon &iconActive;
};
const auto points = std::vector<SpeedPoint>{
{
0.5,
tr::lng_voice_speed_slow,
st::mediaSpeedSlow,
st::mediaSpeedSlowActive },
{
1.0,
tr::lng_voice_speed_normal,
st::mediaSpeedNormal,
st::mediaSpeedNormalActive },
{
1.2,
tr::lng_voice_speed_medium,
st::mediaSpeedMedium,
st::mediaSpeedMediumActive },
{
1.5,
tr::lng_voice_speed_fast,
st::mediaSpeedFast,
st::mediaSpeedFastActive },
{
1.7,
tr::lng_voice_speed_very_fast,
st::mediaSpeedVeryFast,
st::mediaSpeedVeryFastActive },
{
2.0,
tr::lng_voice_speed_super_fast,
st::mediaSpeedSuperFast,
st::mediaSpeedSuperFastActive },
};
for (const auto &point : points) {
const auto speed = point.speed;
const auto text = point.text(tr::now);
const auto icon = &point.icon;
const auto iconActive = &point.iconActive;
auto action = base::make_unique_q<Ui::Menu::Action>(
menu,
st::mediaSpeedMenu.menu,
Ui::Menu::CreateAction(menu, text, [=] { callback(speed); }),
&point.icon,
&point.icon);
const auto raw = action.get();
const auto check = Ui::CreateChild<Ui::RpWidget>(raw);
const auto skip = st.activeCheckSkip;
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);
state->realtime.value(
) | rpl::start_with_next([=](float64 now) {
const auto chosen = (speed == now);
const auto overriden = chosen ? iconActive : icon;
raw->setIcon(overriden, overriden);
raw->action()->setEnabled(!chosen);
check->setVisible(chosen);
}, raw->lifetime());
menu->addAction(std::move(action));
void WithDropdownController::hideTemporarily() {
if (_menu && !_menu->isHidden()) {
_temporarilyHidden = true;
_menu->hide();
}
}
void WithDropdownController::showBack() {
if (_temporarilyHidden) {
_temporarilyHidden = false;
if (_menu && _menu->isHidden()) {
_menu->show();
}
}
}
void WithDropdownController::showMenu() {
if (_menu) {
return;
}
_menu.emplace(_menuParent, _menuSt);
const auto raw = _menu.get();
_menu->events(
) | rpl::start_with_next([this](not_null<QEvent*> e) {
const auto type = e->type();
if (type == QEvent::Enter) {
_menuOverCallback(true);
} else if (type == QEvent::Leave) {
_menuOverCallback(false);
}
}, _menu->lifetime());
_menu->setHiddenCallback([=]{
Ui::PostponeCall(raw, [this] {
_menu = nullptr;
});
});
_button->installEventFilter(raw);
fillMenu(raw);
updateDropdownGeometry();
const auto origin = [&] {
using Origin = Ui::PanelAnimation::Origin;
switch (_menuAlign) {
case style::al_topleft: return Origin::TopLeft;
case style::al_topright: return Origin::TopRight;
case style::al_bottomright: return Origin::BottomRight;
case style::al_bottomleft: return Origin::BottomLeft;
}
Unexpected("Menu align value.");
}();
_menu->showAnimated(origin);
}
OrderController::OrderController(
not_null<Ui::IconButton*> button,
not_null<QWidget*> menuParent,
Fn<void(bool)> menuOverCallback,
rpl::producer<OrderMode> value,
Fn<void(OrderMode)> change)
: WithDropdownController(
button,
menuParent,
st::mediaPlayerMenu,
style::al_topright,
std::move(menuOverCallback))
, _button(button)
, _appOrder(std::move(value))
, _change(std::move(change)) {
button->setClickedCallback([=] {
showMenu();
});
_appOrder.value(
) | rpl::start_with_next([=] {
updateIcon();
}, button->lifetime());
}
void OrderController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
const auto addOrderAction = [&](OrderMode mode) {
struct Fields {
QString label;
const style::icon &icon;
const style::icon &activeIcon;
};
const auto active = (_appOrder.current() == mode);
const auto callback = [change = _change, mode, active] {
change(active ? OrderMode::Default : mode);
};
const auto fields = [&]() -> Fields {
switch (mode) {
case OrderMode::Reverse: return {
.label = tr::lng_audio_player_reverse(tr::now),
.icon = st::mediaPlayerOrderIconReverse,
.activeIcon = st::mediaPlayerOrderIconReverseActive,
};
case OrderMode::Shuffle: return {
.label = tr::lng_audio_player_shuffle(tr::now),
.icon = st::mediaPlayerOrderIconShuffle,
.activeIcon = st::mediaPlayerOrderIconShuffleActive,
};
}
Unexpected("Order mode in addOrderAction.");
}();
menu->addAction(base::make_unique_q<Ui::Menu::Action>(
menu,
(active
? st::mediaPlayerOrderMenuActive
: st::mediaPlayerOrderMenu),
Ui::Menu::CreateAction(menu, fields.label, callback),
&(active ? fields.activeIcon : fields.icon),
&(active ? fields.activeIcon : fields.icon)));
};
addOrderAction(OrderMode::Reverse);
addOrderAction(OrderMode::Shuffle);
}
void OrderController::updateIcon() {
switch (_appOrder.current()) {
case OrderMode::Default:
_button->setIconOverride(
&st::mediaPlayerReverseDisabledIcon,
&st::mediaPlayerReverseDisabledIconOver);
_button->setRippleColorOverride(
&st::mediaPlayerRepeatDisabledRippleBg);
break;
case OrderMode::Reverse:
_button->setIconOverride(&st::mediaPlayerReverseIcon);
_button->setRippleColorOverride(nullptr);
break;
case OrderMode::Shuffle:
_button->setIconOverride(&st::mediaPlayerShuffleIcon);
_button->setRippleColorOverride(nullptr);
break;
}
}
SpeedController::SpeedController(
not_null<SpeedButton*> button,
not_null<QWidget*> menuParent,
Fn<void(bool)> menuOverCallback,
Fn<float64(bool lastNonDefault)> value,
Fn<void(float64)> change)
: WithDropdownController(
button,
menuParent,
button->st().menu.dropdown,
button->st().menuAlign,
std::move(menuOverCallback))
, _st(button->st())
, _lookup(std::move(value))
, _change(std::move(change)) {
button->setClickedCallback([=] {
toggleDefault();
save();
if (const auto current = menu()) {
current->otherEnter();
}
});
setSpeed(_lookup(false));
_speed = _lookup(true);
button->setSpeed(_speed, anim::type::instant);
_speedChanged.events_starting_with(
speed()
) | rpl::start_with_next([=](float64 speed) {
button->setSpeed(speed);
}, button->lifetime());
}
rpl::producer<> SpeedController::saved() const {
return _saved.events();
}
float64 SpeedController::speed() const {
return _isDefault ? 1. : _speed;
}
bool SpeedController::isDefault() const {
return _isDefault;
}
float64 SpeedController::lastNonDefaultSpeed() const {
return _speed;
}
void SpeedController::toggleDefault() {
_isDefault = !_isDefault;
_speedChanged.fire(speed());
}
void SpeedController::setSpeed(float64 newSpeed) {
if (!(_isDefault = (newSpeed == 1.))) {
_speed = newSpeed;
}
_speedChanged.fire(speed());
}
void SpeedController::save() {
_change(speed());
_saved.fire({});
}
void SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
FillSpeedMenu(
menu->menu(),
_st.menu,
_speedChanged.events_starting_with(speed()),
[=](float64 speed) { setSpeed(speed); save(); });
}
} // namespace Media::Player

View file

@ -7,20 +7,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/timer.h"
#include "media/media_common.h"
#include "ui/rp_widget.h"
#include "ui/effects/animations.h"
#include "base/timer.h"
namespace style {
struct MediaSpeedMenu;
struct MediaSpeedButton;
struct DropdownMenu;
} // namespace style
namespace Ui {
class DropdownMenu;
class AbstractButton;
class IconButton;
} // namespace Ui
namespace Ui::Menu {
class Menu;
} // namespace Ui::Menu
namespace Media::Player {
class SpeedButton;
class Dropdown final : public Ui::RpWidget {
public:
explicit Dropdown(QWidget *parent);
@ -57,10 +68,89 @@ private:
};
void FillSpeedMenu(
not_null<Ui::Menu::Menu*> menu,
const style::MediaSpeedMenu &st,
rpl::producer<float64> value,
Fn<void(float64)> callback);
class WithDropdownController {
public:
WithDropdownController(
not_null<Ui::AbstractButton*> button,
not_null<QWidget*> menuParent,
const style::DropdownMenu &menuSt,
Qt::Alignment menuAlign,
Fn<void(bool)> menuOverCallback);
virtual ~WithDropdownController() = default;
[[nodiscard]] not_null<Ui::AbstractButton*> button() const;
Ui::DropdownMenu *menu() const;
void updateDropdownGeometry();
void hideTemporarily();
void showBack();
protected:
void showMenu();
private:
virtual void fillMenu(not_null<Ui::DropdownMenu*> menu) = 0;
const not_null<Ui::AbstractButton*> _button;
const not_null<QWidget*> _menuParent;
const style::DropdownMenu &_menuSt;
const Qt::Alignment _menuAlign = Qt::AlignTop | Qt::AlignRight;
const Fn<void(bool)> _menuOverCallback;
base::unique_qptr<Ui::DropdownMenu> _menu;
bool _temporarilyHidden = false;
bool _overButton = false;
};
class OrderController final : public WithDropdownController {
public:
OrderController(
not_null<Ui::IconButton*> button,
not_null<QWidget*> menuParent,
Fn<void(bool)> menuOverCallback,
rpl::producer<OrderMode> value,
Fn<void(OrderMode)> change);
private:
void fillMenu(not_null<Ui::DropdownMenu*> menu) override;
void updateIcon();
const not_null<Ui::IconButton*> _button;
rpl::variable<OrderMode> _appOrder;
Fn<void(OrderMode)> _change;
};
class SpeedController final : public WithDropdownController {
public:
SpeedController(
not_null<SpeedButton*> button,
not_null<QWidget*> menuParent,
Fn<void(bool)> menuOverCallback,
Fn<float64(bool lastNonDefault)> value,
Fn<void(float64)> change);
[[nodiscard]] rpl::producer<> saved() const;
private:
void fillMenu(not_null<Ui::DropdownMenu*> menu) override;
[[nodiscard]] float64 speed() const;
[[nodiscard]] bool isDefault() const;
[[nodiscard]] float64 lastNonDefaultSpeed() const;
void toggleDefault();
void setSpeed(float64 newSpeed);
void save();
const style::MediaSpeedButton &_st;
Fn<float64(bool lastNonDefault)> _lookup;
Fn<void(float64)> _change;
float64 _speed = kSpedUpDefault;
bool _isDefault = true;
rpl::event_stream<float64> _speedChanged;
rpl::event_stream<> _saved;
};
} // namespace Media::Player

View file

@ -52,13 +52,6 @@ constexpr auto kRememberShuffledOrderItems = 16;
constexpr auto kMinLengthForSavePosition = 20 * TimeId(60); // 20 minutes.
auto VoicePlaybackSpeed() {
return std::clamp(
Core::App().settings().voicePlaybackSpeed(),
Media::Audio::kSpeedMin,
Media::Audio::kSpeedMax);
}
base::options::toggle OptionDisableAutoplayNext({
.id = kOptionDisableAutoplayNext,
.name = "Disable auto-play of the next track",
@ -847,7 +840,7 @@ Streaming::PlaybackOptions Instance::streamingOptions(
? Streaming::Mode::Both
: Streaming::Mode::Audio;
result.speed = audioId.changeablePlaybackSpeed()
? VoicePlaybackSpeed()
? Core::App().settings().voicePlaybackSpeed()
: 1.;
result.audioId = audioId;
if (position >= 0) {
@ -1143,7 +1136,8 @@ void Instance::updateVoicePlaybackSpeed() {
return;
}
if (const auto streamed = data->streamed.get()) {
streamed->instance.setSpeed(VoicePlaybackSpeed());
streamed->instance.setSpeed(
Core::App().settings().voicePlaybackSpeed());
}
}
}

View file

@ -14,6 +14,11 @@ class AudioMsgId;
class DocumentData;
class History;
namespace Media {
enum class RepeatMode;
enum class OrderMode;
} // namespace Media
namespace Media {
namespace Audio {
class Instance;
@ -45,18 +50,6 @@ namespace Player {
extern const char kOptionDisableAutoplayNext[];
enum class RepeatMode {
None,
One,
All,
};
enum class OrderMode {
Default,
Reverse,
Shuffle,
};
class Instance;
struct TrackState;

View file

@ -42,383 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Media {
namespace Player {
class WithDropdownController {
public:
WithDropdownController(
not_null<Ui::AbstractButton*> button,
not_null<Ui::RpWidget*> menuParent,
Fn<void(bool)> menuOverCallback);
virtual ~WithDropdownController() = default;
[[nodiscard]] not_null<Ui::AbstractButton*> button() const;
Ui::DropdownMenu *menu() const;
void updateDropdownGeometry();
void hideTemporarily();
void showBack();
protected:
void showMenu();
private:
virtual void fillMenu(not_null<Ui::DropdownMenu*> menu) = 0;
const not_null<Ui::AbstractButton*> _button;
const not_null<Ui::RpWidget*> _menuParent;
const Fn<void(bool)> _menuOverCallback;
base::unique_qptr<Ui::DropdownMenu> _menu;
bool _temporarilyHidden = false;
bool _overButton = false;
};
class Widget::SpeedButton final : public Ui::RippleButton {
public:
SpeedButton(QWidget *parent);
void setSpeed(float64 speed, anim::type animated = anim::type::normal);
private:
void paintEvent(QPaintEvent *e) override;
QPoint prepareRippleStartPosition() const override;
QImage prepareRippleMask() const override;
SpeedButtonLayout _layout;
QPoint _layoutPosition;
bool _isDefault = false;
};
class Widget::OrderController final : public WithDropdownController {
public:
OrderController(
not_null<Ui::IconButton*> button,
not_null<Ui::RpWidget*> menuParent,
Fn<void(bool)> menuOverCallback);
private:
void fillMenu(not_null<Ui::DropdownMenu*> menu) override;
void updateIcon();
const not_null<Ui::IconButton*> _button;
};
class Widget::SpeedController final : public WithDropdownController {
public:
SpeedController(
not_null<SpeedButton*> button,
not_null<Ui::RpWidget*> menuParent,
Fn<void(bool)> menuOverCallback);
[[nodiscard]] rpl::producer<> saved() const;
private:
void fillMenu(not_null<Ui::DropdownMenu*> menu) override;
[[nodiscard]] float64 speed() const;
[[nodiscard]] bool isDefault() const;
[[nodiscard]] float64 lastNonDefaultSpeed() const;
void toggleDefault();
void setSpeed(float64 newSpeed);
void save();
float64 _speed = 1.7;
bool _isDefault = true;
rpl::event_stream<float64> _speedChanged;
rpl::event_stream<> _saved;
};
WithDropdownController::WithDropdownController(
not_null<Ui::AbstractButton*> button,
not_null<Ui::RpWidget*> menuParent,
Fn<void(bool)> menuOverCallback)
: _button(button)
, _menuParent(menuParent)
, _menuOverCallback(std::move(menuOverCallback)) {
button->events(
) | rpl::filter([=](not_null<QEvent*> e) {
return (e->type() == QEvent::Enter)
|| (e->type() == QEvent::Leave);
}) | rpl::start_with_next([=](not_null<QEvent*> e) {
_overButton = (e->type() == QEvent::Enter);
if (_overButton) {
InvokeQueued(button, [=] {
if (_overButton) {
showMenu();
}
});
}
}, button->lifetime());
}
not_null<Ui::AbstractButton*> WithDropdownController::button() const {
return _button;
}
Ui::DropdownMenu *WithDropdownController::menu() const {
return _menu.get();
}
void WithDropdownController::updateDropdownGeometry() {
if (!_menu) {
return;
}
const auto position = _menu->parentWidget()->mapFromGlobal(
_button->mapToGlobal(
QPoint(_button->width(), _button->height())));
const auto padding = st::mediaPlayerMenu.wrap.padding;
_menu->move(position
- QPoint(_menu->width(), 0)
+ QPoint(padding.right(), -padding.top())
+ st::mediaPlayerMenuPosition);
}
void WithDropdownController::hideTemporarily() {
if (_menu && !_menu->isHidden()) {
_temporarilyHidden = true;
_menu->hide();
}
}
void WithDropdownController::showBack() {
if (_temporarilyHidden) {
_temporarilyHidden = false;
if (_menu && _menu->isHidden()) {
_menu->show();
}
}
}
void WithDropdownController::showMenu() {
if (_menu) {
return;
}
_menu.emplace(_menuParent, st::mediaPlayerMenu);
const auto raw = _menu.get();
_menu->events(
) | rpl::start_with_next([this](not_null<QEvent*> e) {
const auto type = e->type();
if (type == QEvent::Enter) {
_menuOverCallback(true);
} else if (type == QEvent::Leave) {
_menuOverCallback(false);
}
}, _menu->lifetime());
_menu->setHiddenCallback([=]{
Ui::PostponeCall(raw, [this] {
_menu = nullptr;
});
});
_button->installEventFilter(raw);
fillMenu(raw);
updateDropdownGeometry();
_menu->showAnimated(Ui::PanelAnimation::Origin::TopRight);
}
Widget::SpeedButton::SpeedButton(QWidget *parent)
: RippleButton(parent, st::mediaPlayerSpeedRipple)
, _layout(st::mediaSpeedButton, [=] { update(); }, 2.)
, _isDefault(true) {
resize(st::mediaPlayerSpeedSize);
_layoutPosition = QPoint(
(st::mediaPlayerSpeedSize.width() - st::mediaSpeedButton.width) / 2,
st::mediaPlayerSpeedSize.height() - st::mediaSpeedButton.height);
}
void Widget::SpeedButton::setSpeed(float64 speed, anim::type animated) {
_isDefault = (speed == 1.);
_layout.setSpeed(speed);
if (animated == anim::type::instant) {
_layout.finishTransform();
}
update();
}
void Widget::SpeedButton::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
const auto innerHeight = st::mediaSpeedButton.icon.height();
paintRipple(
p,
QPoint(0, height() - innerHeight),
_isDefault ? &st::mediaPlayerSpeedDisabledRippleBg->c : nullptr);
const auto &color = !_isDefault
? st::mediaPlayerActiveFg
: isOver()
? st::menuIconFgOver
: st::menuIconFg;
p.translate(_layoutPosition);
_layout.paint(p, color->c);
}
QPoint Widget::SpeedButton::prepareRippleStartPosition() const {
const auto innerHeight = st::mediaSpeedButton.icon.height();
const auto result = mapFromGlobal(QCursor::pos())
- QPoint(0, height() - innerHeight);
const auto rect = QRect(0, 0, width(), innerHeight);
return rect.contains(result)
? result
: DisabledRippleStartPosition();
}
QImage Widget::SpeedButton::prepareRippleMask() const {
const auto innerHeight = st::mediaSpeedButton.icon.height();
return Ui::RippleAnimation::RoundRectMask(
{ width(), innerHeight },
st::mediaPlayerSpeedRadius);
}
Widget::OrderController::OrderController(
not_null<Ui::IconButton*> button,
not_null<Ui::RpWidget*> menuParent,
Fn<void(bool)> menuOverCallback)
: WithDropdownController(button, menuParent, std::move(menuOverCallback))
, _button(button) {
button->setClickedCallback([=] {
showMenu();
});
Core::App().settings().playerOrderModeValue(
) | rpl::start_with_next([=] {
updateIcon();
}, button->lifetime());
}
void Widget::OrderController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
const auto addOrderAction = [&](OrderMode mode) {
struct Fields {
QString label;
const style::icon &icon;
const style::icon &activeIcon;
};
const auto current = Core::App().settings().playerOrderMode();
const auto active = (current == mode);
const auto callback = [=] {
Core::App().settings().setPlayerOrderMode(active
? OrderMode::Default
: mode);
Core::App().saveSettingsDelayed();
};
const auto fields = [&]() -> Fields {
switch (mode) {
case OrderMode::Reverse: return {
.label = tr::lng_audio_player_reverse(tr::now),
.icon = st::mediaPlayerOrderIconReverse,
.activeIcon = st::mediaPlayerOrderIconReverseActive,
};
case OrderMode::Shuffle: return {
.label = tr::lng_audio_player_shuffle(tr::now),
.icon = st::mediaPlayerOrderIconShuffle,
.activeIcon = st::mediaPlayerOrderIconShuffleActive,
};
}
Unexpected("Order mode in addOrderAction.");
}();
menu->addAction(base::make_unique_q<Ui::Menu::Action>(
menu,
(active
? st::mediaPlayerOrderMenuActive
: st::mediaPlayerOrderMenu),
Ui::Menu::CreateAction(menu, fields.label, callback),
&(active ? fields.activeIcon : fields.icon),
&(active ? fields.activeIcon : fields.icon)));
};
addOrderAction(OrderMode::Reverse);
addOrderAction(OrderMode::Shuffle);
}
void Widget::OrderController::updateIcon() {
switch (Core::App().settings().playerOrderMode()) {
case OrderMode::Default:
_button->setIconOverride(
&st::mediaPlayerReverseDisabledIcon,
&st::mediaPlayerReverseDisabledIconOver);
_button->setRippleColorOverride(
&st::mediaPlayerRepeatDisabledRippleBg);
break;
case OrderMode::Reverse:
_button->setIconOverride(&st::mediaPlayerReverseIcon);
_button->setRippleColorOverride(nullptr);
break;
case OrderMode::Shuffle:
_button->setIconOverride(&st::mediaPlayerShuffleIcon);
_button->setRippleColorOverride(nullptr);
break;
}
}
Widget::SpeedController::SpeedController(
not_null<SpeedButton*> button,
not_null<Ui::RpWidget*> menuParent,
Fn<void(bool)> menuOverCallback)
: WithDropdownController(button, menuParent, std::move(menuOverCallback)) {
button->setClickedCallback([=] {
toggleDefault();
save();
if (const auto current = menu()) {
current->otherEnter();
}
});
setSpeed(Core::App().settings().voicePlaybackSpeed());
_speed = Core::App().settings().voicePlaybackSpeed(true);
button->setSpeed(_speed, anim::type::instant);
_speedChanged.events_starting_with(
speed()
) | rpl::start_with_next([=](float64 speed) {
button->setSpeed(speed);
}, button->lifetime());
}
rpl::producer<> Widget::SpeedController::saved() const {
return _saved.events();
}
float64 Widget::SpeedController::speed() const {
return _isDefault ? 1. : _speed;
}
bool Widget::SpeedController::isDefault() const {
return _isDefault;
}
float64 Widget::SpeedController::lastNonDefaultSpeed() const {
return _speed;
}
void Widget::SpeedController::toggleDefault() {
_isDefault = !_isDefault;
_speedChanged.fire(speed());
}
void Widget::SpeedController::setSpeed(float64 newSpeed) {
if (!(_isDefault = (newSpeed == 1.))) {
_speed = newSpeed;
}
_speedChanged.fire(speed());
}
void Widget::SpeedController::save() {
Core::App().settings().setVoicePlaybackSpeed(speed());
Core::App().saveSettingsDelayed();
_saved.fire({});
}
void Widget::SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
FillSpeedMenu(
menu->menu(),
st::mediaSpeedMenu,
_speedChanged.events_starting_with(speed()),
[=](float64 speed) { setSpeed(speed); save(); });
}
Widget::Widget(
QWidget *parent,
not_null<Ui::RpWidget*> dropdownsParent,
@ -433,7 +56,7 @@ Widget::Widget(
, _volumeToggle(rightControls(), st::mediaPlayerVolumeToggle)
, _repeatToggle(rightControls(), st::mediaPlayerRepeatButton)
, _orderToggle(rightControls(), st::mediaPlayerRepeatButton)
, _speedToggle(rightControls())
, _speedToggle(rightControls(), st::mediaPlayerSpeedButton)
, _close(this, st::mediaPlayerClose)
, _shadow(this)
, _playbackSlider(this, st::mediaPlayerPlayback)
@ -443,12 +66,16 @@ Widget::Widget(
std::make_unique<OrderController>(
_orderToggle.data(),
dropdownsParent,
[=](bool over) { markOver(over); }))
[=](bool over) { markOver(over); },
Core::App().settings().playerOrderModeValue(),
[=](OrderMode value) { saveOrder(value); }))
, _speedController(
std::make_unique<SpeedController>(
_speedToggle.data(),
dropdownsParent,
[=](bool over) { markOver(over); })) {
[=](bool over) { markOver(over); },
[=](bool lastNonDefault) { return speedLookup(lastNonDefault); },
[=](float64 speed) { saveSpeed(speed); })) {
setAttribute(Qt::WA_OpaquePaintEvent);
setMouseTracking(true);
resize(width(), st::mediaPlayerHeight + st::lineWidth);
@ -789,6 +416,20 @@ void Widget::markOver(bool over) {
}
}
void Widget::saveOrder(OrderMode mode) {
Core::App().settings().setPlayerOrderMode(mode);
Core::App().saveSettingsDelayed();
}
float64 Widget::speedLookup(bool lastNonDefault) const {
return Core::App().settings().voicePlaybackSpeed(lastNonDefault);
}
void Widget::saveSpeed(float64 speed) {
Core::App().settings().setVoicePlaybackSpeed(speed);
Core::App().saveSettingsDelayed();
}
void Widget::mouseMoveEvent(QMouseEvent *e) {
updateOverLabelsState(e->pos());
}

View file

@ -23,6 +23,10 @@ template <typename Widget>
class FadeWrap;
} // namespace Ui
namespace Media {
enum class OrderMode;
} // namespace Media
namespace Media::View {
class PlaybackProgress;
} // namespace Media::View
@ -34,6 +38,9 @@ class SessionController;
namespace Media::Player {
class Dropdown;
class SpeedButton;
class OrderController;
class SpeedController;
struct TrackState;
class Widget final : public Ui::RpWidget {
@ -103,6 +110,10 @@ private:
void updateTimeLabel();
void markOver(bool over);
void saveOrder(OrderMode mode);
[[nodiscard]] float64 speedLookup(bool lastNonDefault) const;
void saveSpeed(float64 speed);
const not_null<Window::SessionController*> _controller;
const not_null<Ui::RpWidget*> _orderMenuParent;
@ -128,9 +139,6 @@ private:
bool _wontBeOver = false;
bool _volumeHidden = false;
class SpeedButton;
class OrderController;
class SpeedController;
object_ptr<Ui::FlatLabel> _nameLabel;
object_ptr<Ui::FadeWrap<Ui::RpWidget>> _rightControls;
object_ptr<Ui::LabelSimple> _timeLabel;

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_audio_track.h"
#include "media/streaming/media_streaming_video_track.h"
#include "media/audio/media_audio.h" // for SupportsSpeedControl()
#include "media/media_common.h"
#include "data/data_document.h" // for DocumentData::duration()
namespace Media {
@ -528,8 +529,7 @@ void Player::fail(Error error) {
}
void Player::play(const PlaybackOptions &options) {
Expects(options.speed >= Audio::kSpeedMin
&& options.speed <= Audio::kSpeedMax);
Expects(options.speed >= kSpeedMin && options.speed <= kSpeedMax);
// Looping video with audio is not supported for now.
Expects(!options.loop || (options.mode != Mode::Both));
@ -829,7 +829,7 @@ float64 Player::speed() const {
}
void Player::setSpeed(float64 speed) {
Expects(speed >= Audio::kSpeedMin && speed <= Audio::kSpeedMax);
Expects(speed >= kSpeedMin && speed <= kSpeedMax);
if (!Media::Audio::SupportsSpeedControl()) {
speed = 1.;

View file

@ -26,8 +26,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Media {
namespace {
[[nodiscard]] auto RepeatModeToLoopStatus(Media::Player::RepeatMode mode) {
using Mode = Media::Player::RepeatMode;
[[nodiscard]] auto RepeatModeToLoopStatus(Media::RepeatMode mode) {
using Mode = Media::RepeatMode;
using Status = base::Platform::SystemMediaControls::LoopStatus;
switch (mode) {
case Mode::None: return Status::None;
@ -199,8 +199,8 @@ SystemMediaControlsManager::SystemMediaControlsManager()
_controls->setIsPreviousEnabled(mediaPlayer->previousAvailable(type));
}, _lifetime);
using Media::Player::RepeatMode;
using Media::Player::OrderMode;
using Media::RepeatMode;
using Media::OrderMode;
Core::App().settings().playerRepeatModeValue(
) | rpl::start_with_next([=](RepeatMode mode) {

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_audio_msg_id.h"
#include "media/player/media_player_instance.h"
#include "media/media_common.h"
namespace base::Platform {
class SystemMediaControls;
@ -39,9 +40,9 @@ private:
const std::unique_ptr<base::Platform::SystemMediaControls> _controls;
std::vector<std::shared_ptr<Data::DocumentMedia>> _cachedMediaView;
std::unique_ptr<Media::Streaming::Instance> _streamed;
std::unique_ptr<Streaming::Instance> _streamed;
AudioMsgId _lastAudioMsgId;
Media::Player::OrderMode _lastOrderMode;
OrderMode _lastOrderMode = OrderMode::Default;
rpl::lifetime _lifetimeDownload;
rpl::lifetime _lifetime;

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
using "ui/basic.style";
using "ui/widgets/widgets.style";
using "ui/menu_icons.style";
using "media/player/media_player.style";
mediaviewOverDuration: 150;
@ -24,7 +25,8 @@ mediaviewPlayback: MediaSlider {
seekSize: size(12px, 12px);
duration: mediaviewOverDuration;
}
mediaviewPlaybackTop: 52px;
mediaviewPlaybackTop: 49px;
mediaviewPlayProgressTop: 46px;
mediaviewControlsButton: IconButton {
ripple: RippleAnimation(defaultRippleAnimation) {
@ -34,46 +36,41 @@ mediaviewControlsButton: IconButton {
duration: mediaviewOverDuration;
}
mediaviewControllerSize: size(481px, 75px);
mediaviewControllerSize: size(480px, 72px);
mediaviewPlayProgressLabel: LabelSimple(defaultLabelSimple) {
font: semiboldFont;
font: font(12px semibold);
textFg: mediaviewPlaybackProgressFg;
}
mediaviewPlayProgressSkip: 8px;
mediaviewPlayProgressLeft: 8px;
mediaviewPlayButtonTop: 5px;
mediaviewPlayProgressSkip: 10px;
mediaviewPlayProgressLeft: 4px;
mediaviewPlayButtonTop: 2px;
mediaviewPlayButton: IconButton(mediaviewControlsButton) {
width: 42px;
height: 42px;
rippleAreaSize: 42px;
width: 40px;
height: 40px;
rippleAreaSize: 40px;
icon: icon {{ "player/player_play", mediaviewPlaybackIconFg }};
iconOver: icon {{ "player/player_play", mediaviewPlaybackIconFgOver }};
iconPosition: point(9px, 9px);
iconPosition: point(8px, 8px);
}
mediaviewPauseIcon: icon {{ "player/player_pause", mediaviewPlaybackIconFg }};
mediaviewPauseIconOver: icon {{ "player/player_pause", mediaviewPlaybackIconFgOver }};
mediaviewButtonsTop: 7px;
mediaviewButtonsTop: 6px;
mediaviewButtonsRight: 8px;
mediaviewMenuToggleSkip: 4px;
mediaviewMenuToggle: IconButton(mediaviewControlsButton) {
width: 34px;
height: 34px;
rippleAreaSize: 34px;
icon: icon {{ "player/player_more", mediaviewPlaybackIconFg }};
iconOver: icon {{ "player/player_more", mediaviewPlaybackIconFgOver }};
iconPosition: point(5px, 5px);
}
mediaviewPipButtonSkip: 5px;
mediaviewPipButton: IconButton(mediaviewMenuToggle) {
mediaviewPipButtonSkip: 4px;
mediaviewPipButton: IconButton(mediaviewControlsButton) {
width: 32px;
height: 32px;
rippleAreaSize: 32px;
icon: icon {{ "player/player_pip", mediaviewPlaybackIconFg }};
iconOver: icon {{ "player/player_pip", mediaviewPlaybackIconFgOver }};
iconPosition: point(4px, 4px);
}
mediaviewFullScreenButtonSkip: 8px;
mediaviewFullScreenButton: IconButton(mediaviewMenuToggle) {
mediaviewFullScreenButtonSkip: 4px;
mediaviewFullScreenButton: IconButton(mediaviewPipButton) {
icon: icon {{ "player/player_fullscreen", mediaviewPlaybackIconFg }};
iconOver: icon {{ "player/player_fullscreen", mediaviewPlaybackIconFgOver }};
}
@ -89,17 +86,17 @@ mediaviewVolumeIcon1: icon {{ "player/player_volume_small", mediaviewPlaybackIco
mediaviewVolumeIcon1Over: icon {{ "player/player_volume_small", mediaviewPlaybackIconFgOver }};
mediaviewVolumeIcon2: icon {{ "player/player_volume_on", mediaviewPlaybackIconFg }};
mediaviewVolumeIcon2Over: icon {{ "player/player_volume_on", mediaviewPlaybackIconFgOver }};
mediaviewVolumeTop: 10px;
mediaviewVolumeToggleSkip: 11px;
mediaviewVolumeTop: 6px;
mediaviewVolumeToggleLeft: 6px;
mediaviewVolumeToggle: IconButton(mediaviewControlsButton) {
width: 30px;
height: 30px;
rippleAreaSize: 30px;
width: 32px;
height: 32px;
rippleAreaSize: 32px;
icon: mediaviewVolumeIcon0;
iconOver: mediaviewVolumeIcon0Over;
iconPosition: point(3px, 3px);
iconPosition: point(4px, 4px);
}
mediaviewVolumeSkip: 4px;
mediaviewVolumeSkip: 3px;
mediaviewLeft: icon {
{ "mediaview/next_shadow-flip_horizontal", windowShadowFg }
@ -143,6 +140,9 @@ mediaviewFileIconSize: 80px;
mediaviewFileLink: defaultLinkButton;
mediaviewMenuSeparator: MenuSeparator(defaultMenuSeparator) {
fg: mediaviewMenuFg;
}
mediaviewMenu: Menu(menuWithIcons) {
itemBg: mediaviewMenuBg;
itemBgOver: mediaviewMenuBgOver;
@ -153,9 +153,7 @@ mediaviewMenu: Menu(menuWithIcons) {
itemFgShortcutOver: mediaviewMenuFg;
itemFgShortcutDisabled: mediaviewMenuFg;
separator: MenuSeparator(defaultMenuSeparator) {
fg: mediaviewMenuFg;
}
separator: mediaviewMenuSeparator;
ripple: RippleAnimation(defaultRippleAnimation) {
color: mediaviewMenuBgRipple;
@ -183,39 +181,6 @@ mediaviewDropdownMenu: DropdownMenu(defaultDropdownMenu) {
}
}
mediaviewControlsMenu: Menu(defaultMenu) {
itemBg: mediaviewSaveMsgBg;
itemBgOver: mediaviewPlaybackIconRipple;
itemFg: mediaviewPlaybackProgressFg;
itemFgOver: mediaviewPlaybackProgressFg;
itemFgDisabled: mediaviewPlaybackProgressFg;
itemFgShortcut: mediaviewPlaybackProgressFg;
itemFgShortcutOver: mediaviewPlaybackProgressFg;
itemFgShortcutDisabled: mediaviewPlaybackProgressFg;
separator: MenuSeparator(defaultMenuSeparator) {
fg: mediaviewPlaybackIconRipple;
}
arrow: icon {{ "menu/submenu_arrow", mediaviewPlaybackProgressFg }};
ripple: RippleAnimation(defaultRippleAnimation) {
color: mediaviewPlaybackIconRipple;
}
}
mediaviewControlsMenuShadow: Shadow(defaultEmptyShadow) {
fallback: mediaviewSaveMsgBg;
}
mediaviewControlsPanelAnimation: PanelAnimation(defaultPanelAnimation) {
fadeBg: mediaviewSaveMsgBg;
shadow: mediaviewControlsMenuShadow;
}
mediaviewControlsPopupMenu: PopupMenu(defaultPopupMenu) {
shadow: mediaviewControlsMenuShadow;
menu: mediaviewControlsMenu;
animation: mediaviewControlsPanelAnimation;
}
mediaviewSaveMsgCheck: icon {{ "mediaview/save_check", mediaviewSaveMsgFg }};
mediaviewSaveMsgPadding: margins(55px, 19px, 29px, 20px);
mediaviewSaveMsgCheckPos: point(23px, 21px);
@ -338,6 +303,59 @@ mediaviewTitleMaximizeMacPadding: margins(0px, 4px, 8px, 4px);
mediaviewShadowTop: icon{{ "mediaview/shadow_top", windowShadowFg }};
mediaviewShadowBottom: icon{{ "mediaview/shadow_bottom", windowShadowFg }};
mediaviewSpeedMenu: MediaSpeedMenu(mediaPlayerSpeedMenu) {
dropdown: DropdownMenu(mediaviewDropdownMenu) {
menu: Menu(mediaviewMenu) {
separator: MenuSeparator(mediaviewMenuSeparator) {
fg: mediaviewMenuBgOver;
padding: margins(0px, 4px, 0px, 4px);
width: 6px;
}
itemPadding: margins(54px, 7px, 54px, 9px);
itemFgDisabled: mediaviewTextLinkFg;
}
}
activeCheck: icon {{ "player/player_check", mediaviewTextLinkFg }};
slider: MediaSlider(defaultContinuousSlider) {
activeFg: mediaviewTextLinkFg;
inactiveFg: mediaviewMenuBgOver;
activeFgOver: mediaviewTextLinkFg;
inactiveFgOver: mediaviewMenuBgOver;
activeFgDisabled: mediaviewMenuBgOver;
receivedTillFg: mediaviewMenuBgOver;
width: 6px;
seekSize: size(6px, 6px);
}
slow: mediaSpeedSlow;
slowActive: mediaSpeedSlowActive;
normal: mediaSpeedNormal;
normalActive: mediaSpeedNormalActive;
medium: mediaSpeedMedium;
mediumActive: mediaSpeedMediumActive;
fast: mediaSpeedFast;
fastActive: mediaSpeedFastActive;
veryFast: mediaSpeedVeryFast;
veryFastActive: mediaSpeedVeryFastActive;
superFast: mediaSpeedSuperFast;
superFastActive: mediaSpeedSuperFastActive;
}
mediaviewSpeedButton: MediaSpeedButton(mediaPlayerSpeedButton) {
size: size(32px, 32px);
padding: margins(0px, 0px, 0px, 0px);
fg: mediaviewPlaybackIconFg;
overFg: mediaviewPlaybackIconFgOver;
activeFg: mediaviewTextLinkFg;
icon: icon{{ "player/player_speed", mediaviewPlaybackIconFg }};
ripple: RippleAnimation(defaultRippleAnimation) {
color: mediaviewPlaybackIconRipple;
}
rippleActiveColor: mediaviewPlaybackIconRipple;
rippleRadius: 16px;
menu: mediaviewSpeedMenu;
menuAlign: align(bottomright);
}
themePreviewSize: size(903px, 584px);
themePreviewBg: windowBg;
themePreviewOverlayOpacity: 0.8;

View file

@ -3743,10 +3743,8 @@ void OverlayWidget::playbackControlsSpeedChanged(float64 speed) {
}
}
float64 OverlayWidget::playbackControlsCurrentSpeed() {
const auto result = Core::App().settings().videoPlaybackSpeed();
DEBUG_LOG(("Media playback speed: now %1.").arg(result));
return result;
float64 OverlayWidget::playbackControlsCurrentSpeed(bool lastNonDefault) {
return Core::App().settings().videoPlaybackSpeed(lastNonDefault);
}
void OverlayWidget::switchToPip() {

View file

@ -204,7 +204,7 @@ private:
void playbackControlsVolumeToggled() override;
void playbackControlsVolumeChangeFinished() override;
void playbackControlsSpeedChanged(float64 speed) override;
float64 playbackControlsCurrentSpeed() override;
float64 playbackControlsCurrentSpeed(bool lastNonDefault) override;
void playbackControlsToFullScreen() override;
void playbackControlsFromFullScreen() override;
void playbackControlsToPictureInPicture() override;

View file

@ -8,13 +8,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/view/media_view_playback_controls.h"
#include "media/audio/media_audio.h"
#include "media/player/media_player_button.h"
#include "media/player/media_player_dropdown.h"
#include "media/view/media_view_playback_progress.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/effects/fade_animation.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/menu/menu_item_base.h"
#include "ui/widgets/popup_menu.h"
#include "ui/text/format_values.h"
#include "ui/cached_round_corners.h"
#include "lang/lang_keys.h"
@ -22,184 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Media {
namespace View {
namespace {
constexpr auto kMinSpeed = 50;
constexpr auto kMaxSpeed = 200;
constexpr float64 SpeedShiftToValue(float64 value) {
const auto valueAsSpeedF = value * 100.;
const auto valueAsSpeed = int(valueAsSpeedF + 0.5); // constexpr round.
return float64(valueAsSpeed) / (kMaxSpeed - kMinSpeed);
};
constexpr float64 SpeedToValue(float64 value) {
const auto valueAsSpeedF = value * 100.;
const auto valueAsSpeed = int(valueAsSpeedF + 0.5); // constexpr round.
return float64(valueAsSpeed - kMinSpeed) / (kMaxSpeed - kMinSpeed);
};
constexpr auto kSpeedStickedValues =
std::array<std::pair<float64, float64>, 5>{{
{ SpeedToValue(0.75), SpeedShiftToValue(0.03) },
{ SpeedToValue(1.00), SpeedShiftToValue(0.05) },
{ SpeedToValue(1.25), SpeedShiftToValue(0.03) },
{ SpeedToValue(1.50), SpeedShiftToValue(0.03) },
{ SpeedToValue(1.75), SpeedShiftToValue(0.03) },
}};
class MenuSpeedItem final : public Ui::Menu::ItemBase {
public:
MenuSpeedItem(
not_null<RpWidget*> parent,
const style::Menu &st,
float64 startSpeed);
not_null<QAction*> action() const override;
bool isEnabled() const override;
[[nodiscard]] rpl::producer<float64> changeSpeedRequests() const;
protected:
int contentHeight() const override;
private:
float64 computeSpeed(float64 value) const;
QString speedString(float64 value) const;
QRect _itemRect;
QRect _textRect;
const style::MediaSlider &_sliderSt;
const base::unique_qptr<Ui::MediaSlider> _slider;
const not_null<QAction*> _dummyAction;
const int _lineCount;
const int _height;
rpl::event_stream<float64> _changeSpeedRequests;
};
MenuSpeedItem::MenuSpeedItem(
not_null<RpWidget*> parent,
const style::Menu &st,
float64 startSpeed)
: Ui::Menu::ItemBase(parent, st)
, _sliderSt(st::mediaviewPlayback)
, _slider(base::make_unique_q<Ui::MediaSlider>(
this,
_sliderSt))
, _dummyAction(new QAction(parent))
, _lineCount(std::ceil(st.itemStyle.font->width(speedString(0.9))
/ float(st.widthMax)))
, _height(st.itemPadding.top() * 2
+ st.itemStyle.font->height * _lineCount
+ _sliderSt.seekSize.height()
+ st.itemPadding.bottom() * 2) {
initResizeHook(parent->sizeValue());
enableMouseSelecting();
enableMouseSelecting(_slider.get());
_slider->setAlwaysDisplayMarker(true);
_slider->setValue((base::SafeRound(startSpeed * 100.) - kMinSpeed)
/ (kMaxSpeed - kMinSpeed));
for (const auto &sticked : kSpeedStickedValues) {
_slider->addDivider(sticked.first, st::speedSliderDividerSize);
}
//_slider->addDivider(
// kSpeedStickedValues[1].first,
// st::speedSliderDividerSize);
{
const auto goodWidth = st.itemPadding.left()
+ st.itemPadding.right()
+ st.itemStyle.font->width(speedString(0.9));
setMinWidth(std::clamp(goodWidth, st.widthMin, st.widthMax));
}
sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
const auto geometry = QRect(QPoint(), size);
_itemRect = geometry - st.itemPadding;
const auto height = _itemRect.height();
const auto penWidth = QPen(st.itemBgOver).width();
_textRect = _itemRect
- style::margins(
-penWidth,
0,
-penWidth,
height - st.itemStyle.font->height * _lineCount);
const auto sliderGeometry = _itemRect
- style::margins(0, height - _sliderSt.seekSize.height(), 0, 0);
_slider->setGeometry(sliderGeometry);
}, lifetime());
paintRequest(
) | rpl::start_with_next([=](const QRect &clip) {
auto p = QPainter(this);
const auto selected = isSelected();
p.fillRect(clip, selected ? st.itemBgOver : st.itemBg);
const auto value = _slider->value();
p.setPen(selected ? st.itemFgOver : st.itemFg);
p.setFont(st.itemStyle.font);
p.drawText(_textRect, speedString(value), style::al_left);
}, lifetime());
_slider->setChangeProgressCallback([=](float64 value) {
update(_textRect);
});
_slider->setChangeFinishedCallback([=](float64 value) {
_changeSpeedRequests.fire_copy(computeSpeed(value));
});
_slider->setAdjustCallback([=](float64 value) {
for (const auto &snap : kSpeedStickedValues) {
if (value > (snap.first - snap.second)
&& value < (snap.first + snap.second)) {
return snap.first;
}
}
return value;
});
}
float64 MenuSpeedItem::computeSpeed(float64 value) const {
return anim::interpolate(kMinSpeed, kMaxSpeed, std::clamp(value, 0., 1.))
/ 100.;
}
QString MenuSpeedItem::speedString(float64 value) const {
return tr::lng_mediaview_playback_speed(
tr::now,
lt_speed,
QString::number(computeSpeed(value), 'f', 2) + 'x');
}
not_null<QAction*> MenuSpeedItem::action() const {
return _dummyAction;
}
bool MenuSpeedItem::isEnabled() const {
return true;
}
int MenuSpeedItem::contentHeight() const {
return _height;
}
rpl::producer<float64> MenuSpeedItem::changeSpeedRequests() const {
return _changeSpeedRequests.events();
}
} // namespace
PlaybackControls::PlaybackControls(
QWidget *parent,
@ -211,12 +34,18 @@ PlaybackControls::PlaybackControls(
, _playbackProgress(std::make_unique<PlaybackProgress>())
, _volumeToggle(this, st::mediaviewVolumeToggle)
, _volumeController(this, st::mediaviewPlayback)
, _menuToggle(this, st::mediaviewMenuToggle)
, _speedToggle(this, st::mediaviewSpeedButton)
, _fullScreenToggle(this, st::mediaviewFullScreenButton)
, _pictureInPicture(this, st::mediaviewPipButton)
, _playedAlready(this, st::mediaviewPlayProgressLabel)
, _toPlayLeft(this, st::mediaviewPlayProgressLabel)
, _menuStyle(st::mediaviewControlsPopupMenu)
, _speedController(
std::make_unique<Player::SpeedController>(
_speedToggle.data(),
parent,
[=](bool) {},
[=](bool lastNonDefault) { return speedLookup(lastNonDefault); },
[=](float64 speed) { saveSpeed(speed); }))
, _fadeAnimation(std::make_unique<Ui::FadeAnimation>(this)) {
_fadeAnimation->show();
_fadeAnimation->setFinishedCallback([=] {
@ -229,9 +58,6 @@ PlaybackControls::PlaybackControls(
_pictureInPicture->addClickHandler([=] {
_delegate->playbackControlsToPictureInPicture();
});
_menuToggle->addClickHandler([=] {
showMenu();
});
_volumeController->setValue(_delegate->playbackControlsCurrentVolume());
_volumeController->setChangeProgressCallback([=](float64 value) {
@ -354,33 +180,13 @@ void PlaybackControls::fadeUpdated(float64 opacity) {
_volumeController->setFadeOpacity(opacity);
}
void PlaybackControls::showMenu() {
if (_menu) {
_menu = nullptr;
return;
}
_menu.emplace(this, _menuStyle);
float64 PlaybackControls::speedLookup(bool lastNonDefault) const {
return _delegate->playbackControlsCurrentSpeed(lastNonDefault);
}
if (Media::Audio::SupportsSpeedControl()) {
auto speedItem = base::make_unique_q<MenuSpeedItem>(
_menu,
_menuStyle.menu,
_delegate->playbackControlsCurrentSpeed());
speedItem->changeSpeedRequests(
) | rpl::start_with_next([=](float64 speed) {
updatePlaybackSpeed(speed);
}, speedItem->lifetime());
_menu->addAction(std::move(speedItem));
_menu->addSeparator();
}
_menu->addAction(tr::lng_mediaview_rotate_video(tr::now), [=] {
_delegate->playbackControlsRotate();
});
_menu->setForcedOrigin(Ui::PanelAnimation::Origin::BottomLeft);
_menu->popup(mapToGlobal(_menuToggle->geometry().topLeft()));
void PlaybackControls::saveSpeed(float64 speed) {
_delegate->playbackControlsSpeedChanged(speed);
}
void PlaybackControls::updatePlaybackSpeed(float64 speed) {
@ -524,7 +330,7 @@ void PlaybackControls::setInFullScreen(bool inFullScreen) {
void PlaybackControls::resizeEvent(QResizeEvent *e) {
const auto textSkip = st::mediaviewPlayProgressSkip;
const auto textLeft = st::mediaviewPlayProgressLeft;
const auto textTop = st::mediaviewPlaybackTop + (_playbackSlider->height() - _playedAlready->height()) / 2;
const auto textTop = st::mediaviewPlayProgressTop;
_playedAlready->moveToLeft(textLeft + textSkip, textTop);
_toPlayLeft->moveToRight(textLeft + textSkip, textTop);
const auto remove = 2 * textLeft + 4 * textSkip + _playedAlready->width() + _toPlayLeft->width();
@ -536,16 +342,16 @@ void PlaybackControls::resizeEvent(QResizeEvent *e) {
(width() - _playPauseResume->width()) / 2,
st::mediaviewPlayButtonTop);
auto right = st::mediaviewMenuToggleSkip;
_menuToggle->moveToRight(right, st::mediaviewButtonsTop);
right += _menuToggle->width() + st::mediaviewPipButtonSkip;
auto right = st::mediaviewButtonsRight;
_speedToggle->moveToRight(right, st::mediaviewButtonsTop);
right += _speedToggle->width() + st::mediaviewPipButtonSkip;
_pictureInPicture->moveToRight(right, st::mediaviewButtonsTop);
right += _pictureInPicture->width() + st::mediaviewFullScreenButtonSkip;
_fullScreenToggle->moveToRight(right, st::mediaviewButtonsTop);
updateDownloadProgressPosition();
auto left = st::mediaviewVolumeToggleSkip;
auto left = st::mediaviewVolumeToggleLeft;
_volumeToggle->moveToLeft(left, st::mediaviewVolumeTop);
left += _volumeToggle->width() + st::mediaviewVolumeSkip;
_volumeController->resize(
@ -586,7 +392,7 @@ void PlaybackControls::mousePressEvent(QMouseEvent *e) {
}
bool PlaybackControls::hasMenu() const {
return _menu != nullptr;
return _speedController->menu() != nullptr;
}
bool PlaybackControls::dragging() const {
@ -594,7 +400,7 @@ bool PlaybackControls::dragging() const {
|| _playbackSlider->isChanging()
|| _playPauseResume->isOver()
|| _volumeToggle->isOver()
|| _menuToggle->isOver()
|| _speedToggle->isOver()
|| _fullScreenToggle->isOver()
|| _pictureInPicture->isOver();
}

View file

@ -23,6 +23,8 @@ class PopupMenu;
namespace Media {
namespace Player {
struct TrackState;
class SpeedButton;
class SpeedController;
} // namespace Player
namespace View {
@ -42,7 +44,8 @@ public:
virtual void playbackControlsVolumeToggled() = 0;
virtual void playbackControlsVolumeChangeFinished() = 0;
virtual void playbackControlsSpeedChanged(float64 speed) = 0;
[[nodiscard]] virtual float64 playbackControlsCurrentSpeed() = 0;
[[nodiscard]] virtual float64 playbackControlsCurrentSpeed(
bool lastNonDefault) = 0;
virtual void playbackControlsToFullScreen() = 0;
virtual void playbackControlsFromFullScreen() = 0;
virtual void playbackControlsToPictureInPicture() = 0;
@ -50,6 +53,7 @@ public:
};
PlaybackControls(QWidget *parent, not_null<Delegate*> delegate);
~PlaybackControls();
void showAnimated();
void hideAnimated();
@ -60,8 +64,6 @@ public:
[[nodiscard]] bool hasMenu() const;
[[nodiscard]] bool dragging() const;
~PlaybackControls();
protected:
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
@ -86,9 +88,11 @@ private:
void updatePlayPauseResumeState(const Player::TrackState &state);
void updateTimeTexts(const Player::TrackState &state);
void refreshTimeTexts();
void showMenu();
not_null<Delegate*> _delegate;
[[nodiscard]] float64 speedLookup(bool lastNonDefault) const;
void saveSpeed(float64 speed);
const not_null<Delegate*> _delegate;
bool _inFullScreen = false;
bool _showPause = false;
@ -106,15 +110,13 @@ private:
std::unique_ptr<PlaybackProgress> _receivedTillProgress;
object_ptr<Ui::IconButton> _volumeToggle;
object_ptr<Ui::MediaSlider> _volumeController;
object_ptr<Ui::IconButton> _menuToggle;
object_ptr<Player::SpeedButton> _speedToggle;
object_ptr<Ui::IconButton> _fullScreenToggle;
object_ptr<Ui::IconButton> _pictureInPicture;
object_ptr<Ui::LabelSimple> _playedAlready;
object_ptr<Ui::LabelSimple> _toPlayLeft;
object_ptr<Ui::LabelSimple> _downloadProgress = { nullptr };
const style::PopupMenu &_menuStyle;
base::unique_qptr<Ui::PopupMenu> _menu;
std::unique_ptr<Player::SpeedController> _speedController;
std::unique_ptr<Ui::FadeAnimation> _fadeAnimation;
};

View file

@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
using "ui/basic.style";
using "ui/chat/chat.style";
using "ui/widgets/widgets.style";
using "media/view/media_view.style";
using "boxes/boxes.style";
OverviewFileLayout {

View file

@ -131,3 +131,29 @@ menuIconDisableAttention: icon {{ "menu/disable", menuIconAttentionColor }};
menuIconReportAttention: icon {{ "menu/report", menuIconAttentionColor }};
menuIconBlockSettings: icon {{ "menu/block", windowBgActive }};
playerSpeedSlow: icon {{ "player/speed/audiospeed_menu_0.5", menuIconColor }};
playerSpeedSlowActive: icon {{ "player/speed/audiospeed_menu_0.5", mediaPlayerActiveFg }};
playerSpeedNormal: icon {{ "player/speed/audiospeed_menu_1.0", menuIconColor }};
playerSpeedNormalActive: icon {{ "player/speed/audiospeed_menu_1.0", mediaPlayerActiveFg }};
playerSpeedMedium: icon {{ "player/speed/audiospeed_menu_1.2", menuIconColor }};
playerSpeedMediumActive: icon {{ "player/speed/audiospeed_menu_1.2", mediaPlayerActiveFg }};
playerSpeedFast: icon {{ "player/speed/audiospeed_menu_1.5", menuIconColor }};
playerSpeedFastActive: icon {{ "player/speed/audiospeed_menu_1.5", mediaPlayerActiveFg }};
playerSpeedVeryFast: icon {{ "player/speed/audiospeed_menu_1.7", menuIconColor }};
playerSpeedVeryFastActive: icon {{ "player/speed/audiospeed_menu_1.7", mediaPlayerActiveFg }};
playerSpeedSuperFast: icon {{ "player/speed/audiospeed_menu_2.0", menuIconColor }};
playerSpeedSuperFastActive: icon {{ "player/speed/audiospeed_menu_2.0", mediaPlayerActiveFg }};
mediaSpeedSlow: icon {{ "player/speed/audiospeed_menu_0.5", mediaviewMenuFg }};
mediaSpeedSlowActive: icon {{ "player/speed/audiospeed_menu_0.5", mediaviewTextLinkFg }};
mediaSpeedNormal: icon {{ "player/speed/audiospeed_menu_1.0", mediaviewMenuFg }};
mediaSpeedNormalActive: icon {{ "player/speed/audiospeed_menu_1.0", mediaviewTextLinkFg }};
mediaSpeedMedium: icon {{ "player/speed/audiospeed_menu_1.2", mediaviewMenuFg }};
mediaSpeedMediumActive: icon {{ "player/speed/audiospeed_menu_1.2", mediaviewTextLinkFg }};
mediaSpeedFast: icon {{ "player/speed/audiospeed_menu_1.5", mediaviewMenuFg }};
mediaSpeedFastActive: icon {{ "player/speed/audiospeed_menu_1.5", mediaviewTextLinkFg }};
mediaSpeedVeryFast: icon {{ "player/speed/audiospeed_menu_1.7", mediaviewMenuFg }};
mediaSpeedVeryFastActive: icon {{ "player/speed/audiospeed_menu_1.7", mediaviewTextLinkFg }};
mediaSpeedSuperFast: icon {{ "player/speed/audiospeed_menu_2.0", mediaviewMenuFg }};
mediaSpeedSuperFastActive: icon {{ "player/speed/audiospeed_menu_2.0", mediaviewTextLinkFg }};

View file

@ -125,6 +125,8 @@ PRIVATE
media/player/media_player_dropdown.cpp
media/player/media_player_dropdown.h
media/media_common.h
menu/menu_check_item.cpp
menu/menu_check_item.h
menu/menu_ttl.cpp

@ -1 +1 @@
Subproject commit 62a62d1fb5abe9dc1e6b9f89af1ccef6fef33c52
Subproject commit dec1cd8cea24e396c37c327929c0135d46541626