Add repeat / order controls to the audio player.

This commit is contained in:
John Preston 2021-11-19 15:09:44 +04:00
parent 395100584f
commit 92e2b91f81
10 changed files with 294 additions and 44 deletions

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/section_widget.h"
#include "base/platform/base_platform_info.h"
#include "webrtc/webrtc_create_adm.h"
#include "media/player/media_player_instance.h"
#include "ui/gl/gl_detection.h"
#include "calls/group/calls_group_common.h"
#include "facades.h"
@ -125,7 +126,8 @@ QByteArray Settings::serialize() const {
+ sizeof(qint32) * 2
+ Serialize::bytearraySize(_photoEditorBrush)
+ sizeof(qint32) * 3
+ Serialize::stringSize(_customDeviceModel.current());
+ Serialize::stringSize(_customDeviceModel.current())
+ sizeof(qint32) * 2;
auto result = QByteArray();
result.reserve(size);
@ -223,7 +225,9 @@ QByteArray Settings::serialize() const {
<< qint32(_groupCallNoiseSuppression ? 1 : 0)
<< qint32(_voicePlaybackSpeed * 100)
<< qint32(_closeToTaskbar.current() ? 1 : 0)
<< _customDeviceModel.current();
<< _customDeviceModel.current()
<< qint32(_playerRepeatMode.current())
<< qint32(_playerOrderMode.current());
}
return result;
}
@ -308,6 +312,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
QByteArray photoEditorBrush = _photoEditorBrush;
qint32 closeToTaskbar = _closeToTaskbar.current() ? 1 : 0;
QString customDeviceModel = _customDeviceModel.current();
qint32 playerRepeatMode = static_cast<qint32>(_playerRepeatMode.current());
qint32 playerOrderMode = static_cast<qint32>(_playerOrderMode.current());
stream >> themesAccentColors;
if (!stream.atEnd()) {
@ -471,6 +477,11 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) {
stream >> customDeviceModel;
}
if (!stream.atEnd()) {
stream
>> playerRepeatMode
>> playerOrderMode;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@ -613,6 +624,18 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_photoEditorBrush = photoEditorBrush;
_closeToTaskbar = (closeToTaskbar == 1);
_customDeviceModel = customDeviceModel;
const auto uncheckedPlayerRepeatMode = static_cast<Media::Player::RepeatMode>(playerRepeatMode);
switch (uncheckedPlayerRepeatMode) {
case Media::Player::RepeatMode::None:
case Media::Player::RepeatMode::One:
case Media::Player::RepeatMode::All: _playerRepeatMode = uncheckedPlayerRepeatMode; break;
}
const auto uncheckedPlayerOrderMode = static_cast<Media::Player::OrderMode>(playerOrderMode);
switch (uncheckedPlayerOrderMode) {
case Media::Player::OrderMode::Default:
case Media::Player::OrderMode::Reverse:
case Media::Player::OrderMode::Shuffle: _playerOrderMode = uncheckedPlayerOrderMode; break;
}
}
QString Settings::getSoundPath(const QString &key) const {

View file

@ -32,6 +32,11 @@ 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 {
@ -630,6 +635,31 @@ public:
[[nodiscard]] rpl::producer<QString> deviceModelChanges() const;
[[nodiscard]] rpl::producer<QString> deviceModelValue() const;
void setPlayerRepeatMode(Media::Player::RepeatMode mode) {
_playerRepeatMode = mode;
}
[[nodiscard]] Media::Player::RepeatMode playerRepeatMode() const {
return _playerRepeatMode.current();
}
[[nodiscard]] rpl::producer<Media::Player::RepeatMode> playerRepeatModeValue() const {
return _playerRepeatMode.value();
}
[[nodiscard]] rpl::producer<Media::Player::RepeatMode> playerRepeatModeChanges() const {
return _playerRepeatMode.changes();
}
void setPlayerOrderMode(Media::Player::OrderMode mode) {
_playerOrderMode = mode;
}
[[nodiscard]] Media::Player::OrderMode playerOrderMode() const {
return _playerOrderMode.current();
}
[[nodiscard]] rpl::producer<Media::Player::OrderMode> playerOrderModeValue() const {
return _playerOrderMode.value();
}
[[nodiscard]] rpl::producer<Media::Player::OrderMode> playerOrderModeChanges() const {
return _playerOrderMode.changes();
}
[[nodiscard]] static bool ThirdColumnByDefault();
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
[[nodiscard]] static qint32 SerializePlaybackSpeed(float64 speed) {
@ -731,6 +761,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;
bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window

View file

@ -852,9 +852,7 @@ void MainWidget::createPlayer() {
_controller);
_player->entity()->volumeWidgetCreated(_playerVolume);
_playerRepeat.create(this);
Media::Player::PrepareRepeatDropdown(
_playerRepeat.data(),
_controller);
Media::Player::PrepareRepeatDropdown(_playerRepeat.data());
_player->entity()->repeatWidgetCreated(_playerRepeat);
orderWidgets();
if (_a_show.animating()) {

View file

@ -97,9 +97,21 @@ mediaPlayerShuffleDisabledIconOver: icon {
mediaPlayerRepeatReverseIcon: icon {
{ "player/player_repeat_reverse", mediaPlayerActiveFg, point(9px, 11px)}
};
mediaPlayerRepeatReverseDisabledIcon: icon {
{ "player/player_repeat_reverse", menuIconFg, point(9px, 11px)}
};
mediaPlayerRepeatReverseDisabledIconOver: icon {
{ "player/player_repeat_reverse", menuIconFgOver, point(9px, 11px)}
};
mediaPlayerRepeatShuffleIcon: icon {
{ "player/player_repeat_shuffle", mediaPlayerActiveFg, point(9px, 11px)}
};
mediaPlayerRepeatShuffleDisabledIcon: icon {
{ "player/player_repeat_shuffle", menuIconFg, point(9px, 11px)}
};
mediaPlayerRepeatShuffleDisabledIconOver: icon {
{ "player/player_repeat_shuffle", menuIconFgOver, point(9px, 11px)}
};
mediaPlayerRepeatDisabledRippleBg: windowBgOver;
mediaPlayerSpeedButton: IconButton {

View file

@ -746,7 +746,7 @@ void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) {
}
_updatedNotifier.fire_copy({state});
if (data->isPlaying && state.state == State::StoppedAtEnd) {
if (data->repeatEnabled) {
if (data->repeat.current() == RepeatMode::One) {
play(data->current);
} else if (!moveInPlaylist(data, 1, true)) {
_tracksFinishedNotifier.notify(type);

View file

@ -38,6 +38,18 @@ enum class Error;
namespace Media {
namespace Player {
enum class RepeatMode {
None,
One,
All,
};
enum class OrderMode {
Default,
Reverse,
Shuffle,
};
class Instance;
struct TrackState;
@ -104,16 +116,55 @@ public:
return AudioMsgId();
}
[[nodiscard]] bool repeatEnabled(AudioMsgId::Type type) const {
[[nodiscard]] RepeatMode repeatMode(AudioMsgId::Type type) const {
if (const auto data = getData(type)) {
return data->repeatEnabled;
return data->repeat.current();
}
return false;
return RepeatMode::None;
}
void toggleRepeat(AudioMsgId::Type type) {
[[nodiscard]] rpl::producer<RepeatMode> repeatModeValue(
AudioMsgId::Type type) const {
if (const auto data = getData(type)) {
data->repeatEnabled = !data->repeatEnabled;
_repeatChangedNotifier.notify(type);
return data->repeat.value();
}
return rpl::single(RepeatMode::None);
}
[[nodiscard]] rpl::producer<RepeatMode> repeatModeChanges(
AudioMsgId::Type type) const {
if (const auto data = getData(type)) {
return data->repeat.changes();
}
return rpl::never<RepeatMode>();
}
void setRepeatMode(AudioMsgId::Type type, RepeatMode mode) {
if (const auto data = getData(type)) {
data->repeat = mode;
}
}
[[nodiscard]] OrderMode orderMode(AudioMsgId::Type type) const {
if (const auto data = getData(type)) {
return data->order.current();
}
return OrderMode::Default;
}
[[nodiscard]] rpl::producer<OrderMode> orderModeValue(
AudioMsgId::Type type) const {
if (const auto data = getData(type)) {
return data->order.value();
}
return rpl::single(OrderMode::Default);
}
[[nodiscard]] rpl::producer<OrderMode> orderModeChanges(
AudioMsgId::Type type) const {
if (const auto data = getData(type)) {
return data->order.changes();
}
return rpl::never<OrderMode>();
}
void setOrderMode(AudioMsgId::Type type, OrderMode mode) {
if (const auto data = getData(type)) {
data->order = mode;
}
}
@ -149,9 +200,6 @@ public:
base::Observable<AudioMsgId::Type> &trackChangedNotifier() {
return _trackChangedNotifier;
}
base::Observable<AudioMsgId::Type> &repeatChangedNotifier() {
return _repeatChangedNotifier;
}
rpl::producer<> playlistChanges(AudioMsgId::Type type) const;
@ -192,7 +240,8 @@ private:
History *history = nullptr;
History *migrated = nullptr;
Main::Session *session = nullptr;
bool repeatEnabled = false;
rpl::variable<RepeatMode> repeat = RepeatMode::None;
rpl::variable<OrderMode> order = OrderMode::Default;
bool isPlaying = false;
bool resumeOnCallEnd = false;
std::unique_ptr<Streamed> streamed;
@ -278,7 +327,6 @@ private:
base::Observable<bool> _playerWidgetOver;
base::Observable<AudioMsgId::Type> _tracksFinishedNotifier;
base::Observable<AudioMsgId::Type> _trackChangedNotifier;
base::Observable<AudioMsgId::Type> _repeatChangedNotifier;
rpl::event_stream<AudioMsgId::Type> _playerStopped;
rpl::event_stream<AudioMsgId::Type> _playerStartedPlay;

View file

@ -8,14 +8,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/player/media_player_repeat_controls.h"
#include "media/player/media_player_dropdown.h"
#include "media/player/media_player_instance.h"
#include "ui/widgets/buttons.h"
#include "core/core_settings.h"
#include "core/application.h"
#include "styles/style_media_player.h"
namespace Media::Player {
void PrepareRepeatDropdown(
not_null<Dropdown*> dropdown,
not_null<Window::SessionController*> controller) {
void PrepareRepeatDropdown(not_null<Dropdown*> dropdown) {
const auto makeButton = [&] {
const auto result = Ui::CreateChild<Ui::IconButton>(
dropdown.get(),
@ -25,13 +26,72 @@ void PrepareRepeatDropdown(
};
const auto repeatOne = makeButton();
const auto repeat = makeButton();
const auto repeatAll = makeButton();
const auto shuffle = makeButton();
const auto reverse = makeButton();
repeatOne->setIconOverride(&st::mediaPlayerRepeatOneIcon);
shuffle->setIconOverride(&st::mediaPlayerShuffleIcon);
reverse->setIconOverride(&st::mediaPlayerReverseIcon);
Core::App().settings().playerRepeatModeValue(
) | rpl::start_with_next([=](RepeatMode mode) {
const auto one = (mode == RepeatMode::One);
repeatOne->setIconOverride(one
? &st::mediaPlayerRepeatOneIcon
: &st::mediaPlayerRepeatOneDisabledIcon,
one ? nullptr : &st::mediaPlayerRepeatOneDisabledIconOver);
repeatOne->setRippleColorOverride(
one ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
const auto all = (mode == RepeatMode::All);
repeatAll->setIconOverride(all
? nullptr
: &st::mediaPlayerRepeatDisabledIcon,
all ? nullptr : &st::mediaPlayerRepeatDisabledIconOver);
repeatAll->setRippleColorOverride(
all ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
}, dropdown->lifetime());
Core::App().settings().playerOrderModeValue(
) | rpl::start_with_next([=](OrderMode mode) {
const auto shuffled = (mode == OrderMode::Shuffle);
shuffle->setIconOverride(shuffled
? &st::mediaPlayerShuffleIcon
: &st::mediaPlayerShuffleDisabledIcon,
shuffled ? nullptr : &st::mediaPlayerShuffleDisabledIconOver);
shuffle->setRippleColorOverride(
shuffled ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
const auto reversed = (mode == OrderMode::Reverse);
reverse->setIconOverride(reversed
? &st::mediaPlayerReverseIcon
: &st::mediaPlayerReverseDisabledIcon,
reversed ? nullptr : &st::mediaPlayerReverseDisabledIconOver);
reverse->setRippleColorOverride(
reversed ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
}, dropdown->lifetime());
const auto toggleRepeat = [](RepeatMode mode) {
auto &settings = Core::App().settings();
const auto active = (settings.playerRepeatMode() == mode);
settings.setPlayerRepeatMode(active ? RepeatMode::None : mode);
const auto type = AudioMsgId::Type::Song;
instance()->setRepeatMode(type, settings.playerRepeatMode());
if (!active) {
instance()->setOrderMode(type, settings.playerOrderMode());
}
Core::App().saveSettingsDelayed();
};
const auto toggleOrder = [](OrderMode mode) {
auto &settings = Core::App().settings();
const auto active = (settings.playerOrderMode() == mode);
settings.setPlayerOrderMode(active ? OrderMode::Default : mode);
const auto type = AudioMsgId::Type::Song;
instance()->setOrderMode(type, settings.playerOrderMode());
if (!active) {
instance()->setRepeatMode(type, settings.playerRepeatMode());
}
Core::App().saveSettingsDelayed();
};
repeatOne->setClickedCallback([=] { toggleRepeat(RepeatMode::One); });
repeatAll->setClickedCallback([=] { toggleRepeat(RepeatMode::All); });
shuffle->setClickedCallback([=] { toggleOrder(OrderMode::Shuffle); });
reverse->setClickedCallback([=] { toggleOrder(OrderMode::Reverse); });
dropdown->sizeValue(
) | rpl::start_with_next([=](QSize size) {
@ -44,7 +104,7 @@ void PrepareRepeatDropdown(
top += widget->height() + skip;
};
move(repeatOne);
move(repeat);
move(repeatAll);
move(shuffle);
move(reverse);
}, dropdown->lifetime());

View file

@ -7,16 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Window {
class SessionController;
} // namespace Window
namespace Media::Player {
class Dropdown;
void PrepareRepeatDropdown(
not_null<Dropdown*> dropdown,
not_null<Window::SessionController*> controller);
void PrepareRepeatDropdown(not_null<Dropdown*> dropdown);
} // namespace Media::Player

View file

@ -290,9 +290,33 @@ Widget::Widget(QWidget *parent, not_null<Main::Session*> session)
updateVolumeToggleIcon();
}, lifetime());
updateRepeatTrackIcon();
rpl::combine(
Core::App().settings().playerRepeatModeValue(),
Core::App().settings().playerOrderModeValue(),
instance()->repeatModeValue(AudioMsgId::Type::Song),
instance()->orderModeValue(AudioMsgId::Type::Song)
) | rpl::start_with_next([=] {
updateRepeatToggleIcon();
}, lifetime());
_repeatToggle->setClickedCallback([=] {
instance()->toggleRepeat(AudioMsgId::Type::Song);
const auto type = AudioMsgId::Type::Song;
const auto repeat = Core::App().settings().playerRepeatMode();
const auto order = Core::App().settings().playerOrderMode();
const auto mayBeActive = (repeat != RepeatMode::None)
|| (order != OrderMode::Default);
const auto active = mayBeActive
&& (repeat == instance()->repeatMode(type))
&& (order == instance()->orderMode(type));
if (!active && !mayBeActive) {
Core::App().settings().setPlayerRepeatMode(RepeatMode::All);
Core::App().saveSettingsDelayed();
}
instance()->setRepeatMode(type, active
? RepeatMode::None
: mayBeActive
? repeat
: RepeatMode::All);
instance()->setOrderMode(type, active ? OrderMode::Default : order);
});
_playbackSpeed->saved(
@ -300,11 +324,6 @@ Widget::Widget(QWidget *parent, not_null<Main::Session*> session)
instance()->updateVoicePlaybackSpeed();
}, lifetime());
subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) {
if (type == _type) {
updateRepeatTrackIcon();
}
});
subscribe(instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) {
if (type == _type) {
handleSongChange();
@ -552,10 +571,74 @@ void Widget::updateLabelsGeometry() {
_timeLabel->moveToRight(right, st::mediaPlayerNameTop - st::mediaPlayerTime.font->ascent);
}
void Widget::updateRepeatTrackIcon() {
auto repeating = instance()->repeatEnabled(AudioMsgId::Type::Song);
_repeatToggle->setIconOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledIcon, repeating ? nullptr : &st::mediaPlayerRepeatDisabledIconOver);
_repeatToggle->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
void Widget::updateRepeatToggleIcon() {
const auto type = AudioMsgId::Type::Song;
const auto repeat = Core::App().settings().playerRepeatMode();
const auto order = Core::App().settings().playerOrderMode();
const auto active = (repeat == instance()->repeatMode(type))
&& (order == instance()->orderMode(type))
&& (repeat != RepeatMode::None || order != OrderMode::Default);
switch (repeat) {
case RepeatMode::None:
switch (order) {
case OrderMode::Default:
_repeatToggle->setIconOverride(
&st::mediaPlayerRepeatDisabledIcon,
&st::mediaPlayerRepeatDisabledIconOver);
break;
case OrderMode::Reverse:
_repeatToggle->setIconOverride(
(active
? &st::mediaPlayerReverseIcon
: &st::mediaPlayerReverseDisabledIcon),
active ? nullptr : &st::mediaPlayerRepeatDisabledIconOver);
break;
case OrderMode::Shuffle:
_repeatToggle->setIconOverride(
(active
? &st::mediaPlayerShuffleIcon
: &st::mediaPlayerShuffleDisabledIcon),
active ? nullptr : &st::mediaPlayerShuffleDisabledIconOver);
break;
}
break;
case RepeatMode::One:
_repeatToggle->setIconOverride(
(active
? &st::mediaPlayerRepeatOneIcon
: &st::mediaPlayerRepeatOneDisabledIcon),
active ? nullptr : &st::mediaPlayerRepeatOneDisabledIconOver);
break;
case RepeatMode::All:
switch (order) {
case OrderMode::Default:
_repeatToggle->setIconOverride(
(active ? nullptr : &st::mediaPlayerRepeatDisabledIcon),
(active ? nullptr : &st::mediaPlayerRepeatDisabledIconOver));
break;
case OrderMode::Reverse:
_repeatToggle->setIconOverride(
(active
? &st::mediaPlayerRepeatReverseIcon
: &st::mediaPlayerRepeatReverseDisabledIcon),
(active
? nullptr
: &st::mediaPlayerRepeatReverseDisabledIconOver));
break;
case OrderMode::Shuffle:
_repeatToggle->setIconOverride(
(active
? &st::mediaPlayerRepeatShuffleIcon
: &st::mediaPlayerRepeatShuffleDisabledIcon),
(active
? nullptr
: &st::mediaPlayerRepeatShuffleDisabledIconOver));
break;
}
break;
}
_repeatToggle->setRippleColorOverride(
active ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
}
void Widget::checkForTypeChange() {

View file

@ -78,7 +78,7 @@ private:
void updatePlayPrevNextPositions();
void updateLabelsGeometry();
void updateRepeatTrackIcon();
void updateRepeatToggleIcon();
void updateControlsVisibility();
void updateControlsGeometry();
void createPrevNextButtons();