Implement new voice speed change control design.

This commit is contained in:
John Preston 2023-03-14 17:30:50 +04:00
parent 42d999922f
commit 5ca7b95cdb
84 changed files with 671 additions and 191 deletions

View file

@ -946,8 +946,6 @@ PRIVATE
media/audio/media_audio_track.h
media/audio/media_child_ffmpeg_loader.cpp
media/audio/media_child_ffmpeg_loader.h
media/player/media_player_button.cpp
media/player/media_player_button.h
media/player/media_player_float.cpp
media/player/media_player_float.h
media/player/media_player_instance.cpp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 B

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 B

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 B

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 736 B

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 B

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 775 B

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 B

After

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 995 B

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 B

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 B

After

Width:  |  Height:  |  Size: 847 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 951 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 B

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 B

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 B

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 B

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 609 B

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 B

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 B

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,011 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 731 B

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 924 B

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,020 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,017 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 928 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -3552,8 +3552,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_voice_speed_slow" = "Slow";
"lng_voice_speed_normal" = "Normal";
"lng_voice_speed_medium" = "Medium";
"lng_voice_speed_fast" = "Fast";
"lng_voice_speed_very_fast" = "Very fast";
"lng_voice_speed_super_fast" = "Super fast";
"lng_view_button_user" = "View user";
"lng_view_button_bot" = "View bot";

View file

@ -3193,6 +3193,9 @@ void HistoryItem::createComponents(const MTPDmessage &data) {
}
}
const auto id = data.vreply_to_msg_id().v;
if (data.is_reply_to_scheduled()) {
int a = 0;
}
config.replyTo = data.is_reply_to_scheduled()
? _history->owner().scheduledMessages().localMessageId(id)
: id;

View file

@ -491,7 +491,7 @@ void Mixer::Track::updateWithSpeedPosition() {
int64 Mixer::Track::SpeedIndependentPosition(
int64 position,
float64 speed) {
Expects(speed < 2.5);
Expects(speed <= Audio::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 >= 0.5);
Expects(speed >= Audio::kSpeedMin);
return int64(base::SafeRound(position / speed));
}

View file

@ -10,6 +10,50 @@ using "ui/basic.style";
using "ui/widgets/widgets.style";
using "overview/overview.style";
MediaPlayerButton {
playPosition: point;
playOuter: size;
pausePosition: point;
pauseOuter: size;
pauseStroke: pixels;
cancelPosition: point;
cancelOuter: size;
cancelStroke: pixels;
rippleAreaPosition: point;
rippleAreaSize: pixels;
ripple: RippleAnimation;
duration: int;
}
MediaSpeedButton {
width: pixels;
height: pixels;
font: font;
icon: icon;
}
MediaSpeedMenu {
menu: Menu;
iconFg: color;
iconFgActive: color;
textFgActive: color;
activeCheck: icon;
activeCheckSkip: pixels;
sliderStyle: TextStyle;
sliderPadding: margins;
sliderWidth: pixels;
slider: MediaSlider;
}
mediaSpeedButton: MediaSpeedButton {
width: 24px;
height: 24px;
font: font(11px semibold);
icon: icon{{ "player/player_speed", menuIconFg }};
}
mediaPlayerButton: MediaPlayerButton {
playPosition: point(2px, 0px);
playOuter: size(17px, 15px);
@ -25,6 +69,8 @@ mediaPlayerButton: MediaPlayerButton {
ripple: RippleAnimation(defaultRippleAnimation) {
color: lightButtonBgOver;
}
duration: 200;
}
mediaPlayerWideWidth: 460px;
mediaPlayerHeight: 35px;
@ -96,59 +142,64 @@ mediaPlayerCancelIcon: icon{
{ "player/panel_close", mediaPlayerActiveFg }
};
mediaPlayerSpeedButton: IconButton {
width: 31px;
height: 30px;
icon: icon {
{ "player/voice_speed/voice_speed2", mediaPlayerActiveFg }
};
iconPosition: point(3px, 10px);
rippleAreaPosition: point(3px, 5px);
rippleAreaSize: 25px;
ripple: RippleAnimation(defaultRippleAnimation) {
color: lightButtonBgOver;
}
mediaPlayerSpeedSize: size(30px, 30px);
mediaPlayerSpeedRadius: 4px;
mediaPlayerSpeedRipple: RippleAnimation(defaultRippleAnimation) {
color: lightButtonBgOver;
}
mediaPlayerSpeedDisabledIcon: icon {
{ "player/voice_speed/voice_speed2", menuIconFg }
};
mediaPlayerSpeedDisabledIconOver: icon {
{ "player/voice_speed/voice_speed2", menuIconFgOver }
};
mediaPlayerSpeedSlowIcon: icon {
{ "player/voice_speed/voice_speed0.5", mediaPlayerActiveFg }
};
mediaPlayerSpeedSlowDisabledIcon: icon {
{ "player/voice_speed/voice_speed0.5", menuIconFg }
};
mediaPlayerSpeedSlowDisabledIconOver: icon {
{ "player/voice_speed/voice_speed0.5", menuIconFgOver }
};
mediaPlayerSpeedFastIcon: icon {
{ "player/voice_speed/voice_speed1.5", mediaPlayerActiveFg }
};
mediaPlayerSpeedFastDisabledIcon: icon {
{ "player/voice_speed/voice_speed1.5", menuIconFg }
};
mediaPlayerSpeedFastDisabledIconOver: icon {
{ "player/voice_speed/voice_speed1.5", menuIconFgOver }
};
mediaPlayerSpeedDisabledRippleBg: windowBgOver;
mediaPlayerMenu: DropdownMenu(defaultDropdownMenu) {
wrap: InnerDropdown(defaultInnerDropdown) {
scrollPadding: margins(0px, 8px, 0px, 8px);
scrollPadding: margins(0px, 4px, 0px, 4px);
padding: margins(10px, 2px, 10px, 10px);
}
}
mediaPlayerSpeedMenu: Menu(defaultMenu) {
itemIconPosition: point(6px, 5px);
itemPadding: margins(34px, 8px, 17px, 7px);
}
mediaPlayerMenuCheck: icon {{ "player/player_check", mediaPlayerActiveFg }};
mediaSpeedMenu: MediaSpeedMenu {
menu: Menu(menuWithIcons) {
separator: MenuSeparator(defaultMenuSeparator) {
padding: margins(0px, 4px, 0px, 4px);
width: 6px;
}
itemPadding: margins(54px, 7px, 54px, 9px);
itemFgDisabled: mediaPlayerActiveFg;
}
iconFg: menuIconColor;
iconFgActive: mediaPlayerActiveFg;
textFgActive: mediaPlayerActiveFg;
activeCheck: mediaPlayerMenuCheck;
activeCheckSkip: 8px;
sliderStyle: TextStyle(defaultTextStyle) {
font: font(12px semibold);
}
sliderPadding: margins(50px, 8px, 12px, 8px);
sliderWidth: 122px;
slider: MediaSlider(defaultContinuousSlider) {
activeFg: mediaPlayerActiveFg;
inactiveFg: windowBgOver;
activeFgOver: mediaPlayerActiveFg;
inactiveFgOver: windowBgOver;
activeFgDisabled: windowBgOver;
receivedTillFg: windowBgOver;
width: 6px;
seekSize: size(6px, 6px);
}
}
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 },
};
@ -211,8 +262,6 @@ mediaPlayerPlayback: FilledSlider {
duration: 150;
}
mediaPlayerButtonTransformDuration: 200;
mediaPlayerPanelMarginLeft: 10px;
mediaPlayerPanelMarginBottom: 10px;
mediaPlayerPanelWidth: 344px;

View file

@ -8,18 +8,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/player/media_player_button.h"
#include "ui/painter.h"
#include "styles/style_widgets.h"
#include "styles/style_media_player.h"
namespace Media {
namespace Player {
#include <QtCore/QtMath>
PlayButtonLayout::PlayButtonLayout(const style::MediaPlayerButton &st, Fn<void()> callback)
namespace Media::Player {
namespace {
[[nodiscard]] QString SpeedText(float64 speed) {
return QString::number(base::SafeRound(speed * 10) / 10.) + 'X';
}
} // namespace
PlayButtonLayout::PlayButtonLayout(
const style::MediaPlayerButton &st,
Fn<void()> callback)
: _st(st)
, _callback(std::move(callback)) {
}
void PlayButtonLayout::setState(State state) {
if (_nextState == state) return;
if (_nextState == state) {
return;
}
_nextState = state;
if (!_transformProgress.animating()) {
@ -238,8 +250,95 @@ void PlayButtonLayout::animationCallback() {
}
void PlayButtonLayout::startTransform(float64 from, float64 to) {
_transformProgress.start([this] { animationCallback(); }, from, to, st::mediaPlayerButtonTransformDuration);
_transformProgress.start(
[=] { animationCallback(); },
from,
to,
_st.duration);
}
} // namespace Player
} // namespace Media
SpeedButtonLayout::SpeedButtonLayout(
const style::MediaSpeedButton &st,
Fn<void()> callback,
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;
_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;
}
}
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);
p.setPen(color);
p.setFont(_st.font);
p.drawText(
QPointF(
(_st.width - _textWidth) / 2.,
(_st.height - _metrics.height()) / 2. + _metrics.ascent()),
_text);
}
void SpeedButtonLayout::animationCallback() {
if (!_transformProgress.animating()) {
const auto finalSpeed = _nextSpeed;
_nextSpeed = _speed;
setSpeed(finalSpeed);
}
_callback();
}
void SpeedButtonLayout::startTransform(float64 from, float64 to) {
// No animation for now.
_transformProgress.stop();
animationCallback();
}
} // namespace Media::Player

View file

@ -7,12 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/abstract_button.h"
#include "ui/effects/animations.h"
#include "styles/style_media_player.h"
namespace Media {
namespace Player {
#include <QtGui/QFontMetrics>
namespace style {
struct MediaPlayerButton;
struct MediaSpeedButton;
} // namespace style
namespace Media::Player {
class PlayButtonLayout {
public:
@ -48,5 +52,40 @@ private:
};
} // namespace Player
} // namespace Media
class SpeedButtonLayout {
public:
SpeedButtonLayout(
const style::MediaSpeedButton &st,
Fn<void()> callback,
float64 speed);
void setSpeed(float64 speed);
void finishTransform();
void paint(QPainter &p, const QColor &color);
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;
};
} // namespace Media::Player

View file

@ -7,12 +7,213 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/player/media_player_dropdown.h"
#include "base/timer.h"
#include "lang/lang_keys.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/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) {
return (speed - kSpeedMin) / (kSpeedMax - kSpeedMin);
}
[[nodiscard]] float64 SliderValueToSpeed(float64 value) {
const auto speed = value * (kSpeedMax - kSpeedMin) + kSpeedMin;
return base::SafeRound(speed * 10) / 10.;
}
constexpr auto kSpeedStickedValues =
std::array<std::pair<float64, float64>, 7>{{
{ 0.8, 0.05 },
{ 1.0, 0.05 },
{ 1.2, 0.05 },
{ 1.5, 0.05 },
{ 1.7, 0.05 },
{ 2.0, 0.05 },
{ 2.2, 0.05 },
}};
class SpeedSliderItem final : public Ui::Menu::ItemBase {
public:
SpeedSliderItem(
not_null<RpWidget*> parent,
const style::MediaSpeedMenu &st,
rpl::producer<float64> value);
not_null<QAction*> action() const override;
bool isEnabled() const override;
[[nodiscard]] float64 current() const;
[[nodiscard]] rpl::producer<float64> changing() const;
[[nodiscard]] rpl::producer<float64> changed() const;
[[nodiscard]] rpl::producer<float64> debouncedChanges() const;
protected:
int contentHeight() const override;
private:
void setExternalValue(float64 speed);
void setSliderValue(float64 speed);
const base::unique_qptr<Ui::MediaSlider> _slider;
const not_null<QAction*> _dummyAction;
const style::MediaSpeedMenu &_st;
Ui::Text::String _text;
int _height = 0;
rpl::event_stream<float64> _changing;
rpl::event_stream<float64> _changed;
rpl::event_stream<float64> _debounced;
base::Timer _debounceTimer;
rpl::variable<float64> _last = 0.;
};
SpeedSliderItem::SpeedSliderItem(
not_null<RpWidget*> parent,
const style::MediaSpeedMenu &st,
rpl::producer<float64> value)
: Ui::Menu::ItemBase(parent, st.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.sliderPadding.bottom())
, _debounceTimer([=] { _debounced.fire(current()); }) {
initResizeHook(parent->sizeValue());
enableMouseSelecting();
enableMouseSelecting(_slider.get());
setMinWidth(st.sliderPadding.left()
+ st.sliderWidth
+ st.sliderPadding.right());
_slider->setAlwaysDisplayMarker(true);
sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
const auto geometry = QRect(QPoint(), size);
const auto padding = _st.sliderPadding;
const auto inner = geometry - padding;
_slider->setGeometry(
padding.left(),
inner.y(),
(geometry.width() - padding.left() - padding.right()),
inner.height());
}, lifetime());
paintRequest(
) | rpl::start_with_next([=](const QRect &clip) {
auto p = Painter(this);
p.fillRect(clip, _st.menu.itemBg);
const auto left = (_st.sliderPadding.left() - _text.maxWidth()) / 2;
const auto top = _st.menu.itemPadding.top();
p.setPen(_st.menu.itemFg);
_text.drawLeftElided(p, left, top, _text.maxWidth(), width());
}, lifetime());
_slider->setChangeProgressCallback([=](float64 value) {
const auto speed = SliderValueToSpeed(value);
if (current() != speed) {
_last = speed;
_changing.fire_copy(speed);
_debounceTimer.callOnce(kSpeedDebounceTimeout);
}
});
_slider->setChangeFinishedCallback([=](float64 value) {
const auto speed = SliderValueToSpeed(value);
_last = speed;
_changed.fire_copy(speed);
_debounced.fire_copy(speed);
_debounceTimer.cancel();
});
std::move(
value
) | rpl::start_with_next([=](float64 external) {
setExternalValue(external);
}, lifetime());
_last.value(
) | rpl::start_with_next([=](float64 value) {
const auto text = QString::number(value, 'f', 1) + 'x';
if (_text.toString() != text) {
_text.setText(_st.sliderStyle, text);
update();
}
}, lifetime());
_slider->setAdjustCallback([=](float64 value) {
const auto speed = SliderValueToSpeed(value);
for (const auto &snap : kSpeedStickedValues) {
if (speed > (snap.first - snap.second)
&& speed < (snap.first + snap.second)) {
return SpeedToSliderValue(snap.first);
}
}
return value;
});
}
void SpeedSliderItem::setExternalValue(float64 speed) {
if (!_slider->isChanging()) {
setSliderValue(speed);
}
}
void SpeedSliderItem::setSliderValue(float64 speed) {
const auto value = SpeedToSliderValue(speed);
_slider->setValue(value);
_last = speed;
_changed.fire_copy(speed);
}
not_null<QAction*> SpeedSliderItem::action() const {
return _dummyAction;
}
bool SpeedSliderItem::isEnabled() const {
return false;
}
int SpeedSliderItem::contentHeight() const {
return _height;
}
float64 SpeedSliderItem::current() const {
return _last.current();
}
rpl::producer<float64> SpeedSliderItem::changing() const {
return _changing.events();
}
rpl::producer<float64> SpeedSliderItem::changed() const {
return _changed.events();
}
rpl::producer<float64> SpeedSliderItem::debouncedChanges() const {
return _debounced.events();
}
} // namespace
Dropdown::Dropdown(QWidget *parent)
: RpWidget(parent)
@ -178,4 +379,109 @@ 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));
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.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::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));
}
}
} // namespace Media::Player

View file

@ -11,6 +11,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "base/timer.h"
namespace style {
struct MediaSpeedMenu;
} // namespace style
namespace Ui::Menu {
class Menu;
} // namespace Ui::Menu
namespace Media::Player {
class Dropdown final : public Ui::RpWidget {
@ -49,4 +57,10 @@ private:
};
void FillSpeedMenu(
not_null<Ui::Menu::Menu*> menu,
const style::MediaSpeedMenu &st,
rpl::producer<float64> value,
Fn<void(float64)> callback);
} // namespace Media::Player

View file

@ -31,12 +31,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/player/media_player_instance.h"
#include "media/player/media_player_dropdown.h"
#include "media/player/media_player_volume_controller.h"
#include "styles/style_media_player.h"
#include "styles/style_media_view.h"
#include "history/history_item.h"
#include "history/history_item_helpers.h"
#include "storage/storage_account.h"
#include "main/main_session.h"
#include "styles/style_media_player.h"
#include "styles/style_media_view.h"
#include "styles/style_chat.h" // expandedMenuSeparator.
namespace Media {
namespace Player {
@ -44,12 +45,12 @@ namespace Player {
class WithDropdownController {
public:
WithDropdownController(
not_null<Ui::IconButton*> button,
not_null<Ui::AbstractButton*> button,
not_null<Ui::RpWidget*> menuParent,
Fn<void(bool)> menuOverCallback);
virtual ~WithDropdownController() = default;
[[nodiscard]] not_null<Ui::IconButton*> button() const;
[[nodiscard]] not_null<Ui::AbstractButton*> button() const;
Ui::DropdownMenu *menu() const;
void updateDropdownGeometry();
@ -63,7 +64,7 @@ protected:
private:
virtual void fillMenu(not_null<Ui::DropdownMenu*> menu) = 0;
const not_null<Ui::IconButton*> _button;
const not_null<Ui::AbstractButton*> _button;
const not_null<Ui::RpWidget*> _menuParent;
const Fn<void(bool)> _menuOverCallback;
base::unique_qptr<Ui::DropdownMenu> _menu;
@ -72,6 +73,24 @@ private:
};
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;
QImage prepareRippleMask() const override;
QPoint prepareRippleStartPosition() const override;
SpeedButtonLayout _layout;
QPoint _layoutPosition;
bool _isDefault = false;
};
class Widget::OrderController final : public WithDropdownController {
public:
OrderController(
@ -83,12 +102,14 @@ 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<Ui::IconButton*> button,
not_null<SpeedButton*> button,
not_null<Ui::RpWidget*> menuParent,
Fn<void(bool)> menuOverCallback);
@ -96,7 +117,6 @@ public:
private:
void fillMenu(not_null<Ui::DropdownMenu*> menu) override;
void updateIcon();
[[nodiscard]] float64 speed() const;
[[nodiscard]] bool isDefault() const;
@ -105,7 +125,7 @@ private:
void setSpeed(float64 newSpeed);
void save();
float64 _speed = 2.;
float64 _speed = 1.7;
bool _isDefault = true;
rpl::event_stream<float64> _speedChanged;
rpl::event_stream<> _saved;
@ -113,7 +133,7 @@ private:
};
WithDropdownController::WithDropdownController(
not_null<Ui::IconButton*> button,
not_null<Ui::AbstractButton*> button,
not_null<Ui::RpWidget*> menuParent,
Fn<void(bool)> menuOverCallback)
: _button(button)
@ -135,7 +155,7 @@ WithDropdownController::WithDropdownController(
}, button->lifetime());
}
not_null<Ui::IconButton*> WithDropdownController::button() const {
not_null<Ui::AbstractButton*> WithDropdownController::button() const {
return _button;
}
@ -199,11 +219,48 @@ void WithDropdownController::showMenu() {
_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);
paintRipple(
p,
QPoint(),
_isDefault ? &st::mediaPlayerSpeedDisabledRippleBg->c : nullptr);
const auto &color = !_isDefault
? st::mediaPlayerActiveFg
: isOver()
? st::menuIconFgOver
: st::menuIconFg;
p.translate(_layoutPosition);
_layout.paint(p, color->c);
}
Widget::OrderController::OrderController(
not_null<Ui::IconButton*> button,
not_null<Ui::RpWidget*> menuParent,
Fn<void(bool)> menuOverCallback)
: WithDropdownController(button, menuParent, std::move(menuOverCallback)) {
: WithDropdownController(button, menuParent, std::move(menuOverCallback))
, _button(button) {
button->setClickedCallback([=] {
showMenu();
});
@ -260,25 +317,25 @@ void Widget::OrderController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
void Widget::OrderController::updateIcon() {
switch (Core::App().settings().playerOrderMode()) {
case OrderMode::Default:
button()->setIconOverride(
_button->setIconOverride(
&st::mediaPlayerReverseDisabledIcon,
&st::mediaPlayerReverseDisabledIconOver);
button()->setRippleColorOverride(
_button->setRippleColorOverride(
&st::mediaPlayerRepeatDisabledRippleBg);
break;
case OrderMode::Reverse:
button()->setIconOverride(&st::mediaPlayerReverseIcon);
button()->setRippleColorOverride(nullptr);
_button->setIconOverride(&st::mediaPlayerReverseIcon);
_button->setRippleColorOverride(nullptr);
break;
case OrderMode::Shuffle:
button()->setIconOverride(&st::mediaPlayerShuffleIcon);
button()->setRippleColorOverride(nullptr);
_button->setIconOverride(&st::mediaPlayerShuffleIcon);
_button->setRippleColorOverride(nullptr);
break;
}
}
Widget::SpeedController::SpeedController(
not_null<Ui::IconButton*> button,
not_null<SpeedButton*> button,
not_null<Ui::RpWidget*> menuParent,
Fn<void(bool)> menuOverCallback)
: WithDropdownController(button, menuParent, std::move(menuOverCallback)) {
@ -293,45 +350,15 @@ Widget::SpeedController::SpeedController(
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([=] {
updateIcon();
) | rpl::start_with_next([=](float64 speed) {
button->setSpeed(speed);
}, button->lifetime());
}
void Widget::SpeedController::updateIcon() {
const auto isDefaultSpeed = isDefault();
const auto nonDefaultSpeed = lastNonDefaultSpeed();
if (nonDefaultSpeed == .5) {
button()->setIconOverride(
(isDefaultSpeed
? &st::mediaPlayerSpeedSlowDisabledIcon
: &st::mediaPlayerSpeedSlowIcon),
(isDefaultSpeed
? &st::mediaPlayerSpeedSlowDisabledIconOver
: &st::mediaPlayerSpeedSlowIcon));
} else if (nonDefaultSpeed == 1.5) {
button()->setIconOverride(
(isDefaultSpeed
? &st::mediaPlayerSpeedFastDisabledIcon
: &st::mediaPlayerSpeedFastIcon),
(isDefaultSpeed
? &st::mediaPlayerSpeedFastDisabledIconOver
: &st::mediaPlayerSpeedFastIcon));
} else {
button()->setIconOverride(
isDefaultSpeed ? &st::mediaPlayerSpeedDisabledIcon : nullptr,
(isDefaultSpeed
? &st::mediaPlayerSpeedDisabledIconOver
: nullptr));
}
button()->setRippleColorOverride(isDefaultSpeed
? &st::mediaPlayerSpeedDisabledRippleBg
: nullptr);
}
rpl::producer<> Widget::SpeedController::saved() const {
return _saved.events();
}
@ -367,35 +394,11 @@ void Widget::SpeedController::save() {
}
void Widget::SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
const auto currentSpeed = speed();
const auto addSpeedAction = [&](float64 speed, QString text) {
const auto callback = [=] {
setSpeed(speed);
save();
};
const auto icon = (speed == currentSpeed)
? &st::mediaPlayerMenuCheck
: nullptr;
auto action = base::make_unique_q<Ui::Menu::Action>(
menu,
st::mediaPlayerSpeedMenu,
Ui::Menu::CreateAction(menu, text, callback),
icon,
icon);
const auto raw = action.get();
_speedChanged.events(
) | rpl::start_with_next([=](float64 updatedSpeed) {
const auto icon = (speed == updatedSpeed)
? &st::mediaPlayerMenuCheck
: nullptr;
raw->setIcon(icon, icon);
}, raw->lifetime());
menu->addAction(std::move(action));
};
addSpeedAction(0.5, tr::lng_voice_speed_slow(tr::now));
addSpeedAction(1., tr::lng_voice_speed_normal(tr::now));
addSpeedAction(1.5, tr::lng_voice_speed_fast(tr::now));
addSpeedAction(2., tr::lng_voice_speed_very_fast(tr::now));
FillSpeedMenu(
menu->menu(),
st::mediaSpeedMenu,
_speedChanged.events_starting_with(speed()),
[=](float64 speed) { setSpeed(speed); save(); });
}
Widget::Widget(
@ -412,7 +415,7 @@ Widget::Widget(
, _volumeToggle(rightControls(), st::mediaPlayerVolumeToggle)
, _repeatToggle(rightControls(), st::mediaPlayerRepeatButton)
, _orderToggle(rightControls(), st::mediaPlayerRepeatButton)
, _speedToggle(rightControls(), st::mediaPlayerSpeedButton)
, _speedToggle(rightControls())
, _close(this, st::mediaPlayerClose)
, _shadow(this)
, _playbackSlider(this, st::mediaPlayerPlayback)

View file

@ -23,21 +23,16 @@ template <typename Widget>
class FadeWrap;
} // namespace Ui
namespace Media {
namespace View {
namespace Media::View {
class PlaybackProgress;
} // namespace Clip
} // namespace Media
} // namespace Media::View
namespace Window {
class SessionController;
} // namespace Window
namespace Media {
namespace Player {
namespace Media::Player {
class PlayButton;
class SpeedButton;
class Dropdown;
struct TrackState;
@ -47,6 +42,7 @@ public:
QWidget *parent,
not_null<Ui::RpWidget*> dropdownsParent,
not_null<Window::SessionController*> controller);
~Widget();
void setCloseCallback(Fn<void()> callback);
void setShowItemCallback(Fn<void(not_null<const HistoryItem*>)> callback);
@ -61,8 +57,6 @@ public:
return _togglePlaylistRequests.events();
}
~Widget();
private:
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
@ -134,7 +128,7 @@ private:
bool _wontBeOver = false;
bool _volumeHidden = false;
class PlayButton;
class SpeedButton;
class OrderController;
class SpeedController;
object_ptr<Ui::FlatLabel> _nameLabel;
@ -146,7 +140,7 @@ private:
object_ptr<Ui::IconButton> _volumeToggle;
object_ptr<Ui::IconButton> _repeatToggle;
object_ptr<Ui::IconButton> _orderToggle;
object_ptr<Ui::IconButton> _speedToggle;
object_ptr<SpeedButton> _speedToggle;
object_ptr<Ui::IconButton> _close;
object_ptr<Ui::PlainShadow> _shadow = { nullptr };
object_ptr<Ui::FilledSlider> _playbackSlider;
@ -159,5 +153,4 @@ private:
};
} // namespace Player
} // namespace Media
} // namespace Media::Player

View file

@ -528,7 +528,8 @@ void Player::fail(Error error) {
}
void Player::play(const PlaybackOptions &options) {
Expects(options.speed >= 0.5 && options.speed <= 2.);
Expects(options.speed >= Audio::kSpeedMin
&& options.speed <= Audio::kSpeedMax);
// Looping video with audio is not supported for now.
Expects(!options.loop || (options.mode != Mode::Both));
@ -828,7 +829,7 @@ float64 Player::speed() const {
}
void Player::setSpeed(float64 speed) {
Expects(speed >= 0.5 && speed <= 2.);
Expects(speed >= Audio::kSpeedMin && speed <= Audio::kSpeedMax);
if (!Media::Audio::SupportsSpeedControl()) {
speed = 1.;

View file

@ -1093,9 +1093,6 @@ void Pip::handleMousePress(QPoint position, Qt::MouseButton button) {
}
void Pip::handleMouseRelease(QPoint position, Qt::MouseButton button) {
Expects(1 && _delegate->pipPlaybackSpeed() >= 0.5
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
const auto weak = Ui::MakeWeak(_panel.widget());
const auto guard = gsl::finally([&] {
if (weak) {
@ -1107,21 +1104,11 @@ void Pip::handleMouseRelease(QPoint position, Qt::MouseButton button) {
}
seekUpdate(position);
Assert(2 && _delegate->pipPlaybackSpeed() >= 0.5
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
volumeControllerUpdate(position);
Assert(3 && _delegate->pipPlaybackSpeed() >= 0.5
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
const auto pressed = base::take(_pressed);
if (pressed && *pressed == OverState::Playback) {
_panel.setDragDisabled(false);
Assert(4 && _delegate->pipPlaybackSpeed() >= 0.5
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
seekFinish(_playbackProgress->value());
} else if (pressed && *pressed == OverState::VolumeController) {
_panel.setDragDisabled(false);
@ -1182,9 +1169,6 @@ void Pip::seekProgress(float64 value) {
}
void Pip::seekFinish(float64 value) {
Expects(5 && _delegate->pipPlaybackSpeed() >= 0.5
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
if (!_lastDurationMs) {
return;
}
@ -1671,31 +1655,18 @@ void Pip::playbackPauseResume() {
}
void Pip::restartAtSeekPosition(crl::time position) {
Expects(6 && _delegate->pipPlaybackSpeed() >= 0.5
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
if (!_instance.info().video.cover.isNull()) {
_preparedCoverStorage = QImage();
_preparedCoverState = ThumbState::Empty;
_instance.saveFrameToCover();
}
Assert(7 && _delegate->pipPlaybackSpeed() >= 0.5
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
auto options = Streaming::PlaybackOptions();
options.position = position;
options.hwAllowed = Core::App().settings().hardwareAcceleratedVideo();
options.audioId = _instance.player().prepareLegacyState().id;
Assert(8 && _delegate->pipPlaybackSpeed() >= 0.5
&& _delegate->pipPlaybackSpeed() <= 2.); // Debugging strange crash.
options.speed = _delegate->pipPlaybackSpeed();
Assert(9 && options.speed >= 0.5
&& options.speed <= 2.); // Debugging strange crash.
_instance.play(options);
if (_startPaused) {
_instance.pause();

View file

@ -120,6 +120,8 @@ PRIVATE
media/clip/media_clip_reader.cpp
media/clip/media_clip_reader.h
media/player/media_player_button.cpp
media/player/media_player_button.h
media/player/media_player_dropdown.cpp
media/player/media_player_dropdown.h

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