From 92e2b91f81caec82ef8e0f22eb179e221c7aef15 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 19 Nov 2021 15:09:44 +0400 Subject: [PATCH] Add repeat / order controls to the audio player. --- Telegram/SourceFiles/core/core_settings.cpp | 27 ++++- Telegram/SourceFiles/core/core_settings.h | 32 ++++++ Telegram/SourceFiles/mainwidget.cpp | 4 +- .../media/player/media_player.style | 12 ++ .../media/player/media_player_instance.cpp | 2 +- .../media/player/media_player_instance.h | 70 ++++++++++-- .../player/media_player_repeat_controls.cpp | 76 +++++++++++-- .../player/media_player_repeat_controls.h | 8 +- .../media/player/media_player_widget.cpp | 105 ++++++++++++++++-- .../media/player/media_player_widget.h | 2 +- 10 files changed, 294 insertions(+), 44 deletions(-) diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index 05eeab2548..1863fb27d0 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -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(_playerRepeatMode.current()); + qint32 playerOrderMode = static_cast(_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(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(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 { diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 8de40b6a31..b36112fa40 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -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 deviceModelChanges() const; [[nodiscard]] rpl::producer deviceModelValue() const; + void setPlayerRepeatMode(Media::Player::RepeatMode mode) { + _playerRepeatMode = mode; + } + [[nodiscard]] Media::Player::RepeatMode playerRepeatMode() const { + return _playerRepeatMode.current(); + } + [[nodiscard]] rpl::producer playerRepeatModeValue() const { + return _playerRepeatMode.value(); + } + [[nodiscard]] rpl::producer 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 playerOrderModeValue() const { + return _playerOrderMode.value(); + } + [[nodiscard]] rpl::producer 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 _hiddenGroupCallTooltips; rpl::variable _closeToTaskbar = false; rpl::variable _customDeviceModel; + rpl::variable _playerRepeatMode; + rpl::variable _playerOrderMode; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream _tabbedReplacedWithInfoValue; // per-window diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 846dd8a589..f2f776d764 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -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()) { diff --git a/Telegram/SourceFiles/media/player/media_player.style b/Telegram/SourceFiles/media/player/media_player.style index 22a726790a..3a3392c554 100644 --- a/Telegram/SourceFiles/media/player/media_player.style +++ b/Telegram/SourceFiles/media/player/media_player.style @@ -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 { diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 7410ed7f1a..099feec2ee 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -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); diff --git a/Telegram/SourceFiles/media/player/media_player_instance.h b/Telegram/SourceFiles/media/player/media_player_instance.h index 47eb928980..39b9ce5133 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.h +++ b/Telegram/SourceFiles/media/player/media_player_instance.h @@ -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 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 repeatModeChanges( + AudioMsgId::Type type) const { + if (const auto data = getData(type)) { + return data->repeat.changes(); + } + return rpl::never(); + } + 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 orderModeValue( + AudioMsgId::Type type) const { + if (const auto data = getData(type)) { + return data->order.value(); + } + return rpl::single(OrderMode::Default); + } + [[nodiscard]] rpl::producer orderModeChanges( + AudioMsgId::Type type) const { + if (const auto data = getData(type)) { + return data->order.changes(); + } + return rpl::never(); + } + void setOrderMode(AudioMsgId::Type type, OrderMode mode) { + if (const auto data = getData(type)) { + data->order = mode; } } @@ -149,9 +200,6 @@ public: base::Observable &trackChangedNotifier() { return _trackChangedNotifier; } - base::Observable &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 repeat = RepeatMode::None; + rpl::variable order = OrderMode::Default; bool isPlaying = false; bool resumeOnCallEnd = false; std::unique_ptr streamed; @@ -278,7 +327,6 @@ private: base::Observable _playerWidgetOver; base::Observable _tracksFinishedNotifier; base::Observable _trackChangedNotifier; - base::Observable _repeatChangedNotifier; rpl::event_stream _playerStopped; rpl::event_stream _playerStartedPlay; diff --git a/Telegram/SourceFiles/media/player/media_player_repeat_controls.cpp b/Telegram/SourceFiles/media/player/media_player_repeat_controls.cpp index fe8bfc8826..302852dd0a 100644 --- a/Telegram/SourceFiles/media/player/media_player_repeat_controls.cpp +++ b/Telegram/SourceFiles/media/player/media_player_repeat_controls.cpp @@ -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, - not_null controller) { +void PrepareRepeatDropdown(not_null dropdown) { const auto makeButton = [&] { const auto result = Ui::CreateChild( 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()); diff --git a/Telegram/SourceFiles/media/player/media_player_repeat_controls.h b/Telegram/SourceFiles/media/player/media_player_repeat_controls.h index ae3f31de96..17cac68682 100644 --- a/Telegram/SourceFiles/media/player/media_player_repeat_controls.h +++ b/Telegram/SourceFiles/media/player/media_player_repeat_controls.h @@ -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, - not_null controller); +void PrepareRepeatDropdown(not_null dropdown); } // namespace Media::Player diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index a28481de8c..5083f83a64 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -290,9 +290,33 @@ Widget::Widget(QWidget *parent, not_null 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 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() { diff --git a/Telegram/SourceFiles/media/player/media_player_widget.h b/Telegram/SourceFiles/media/player/media_player_widget.h index e918835e93..7b8f91301b 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.h +++ b/Telegram/SourceFiles/media/player/media_player_widget.h @@ -78,7 +78,7 @@ private: void updatePlayPrevNextPositions(); void updateLabelsGeometry(); - void updateRepeatTrackIcon(); + void updateRepeatToggleIcon(); void updateControlsVisibility(); void updateControlsGeometry(); void createPrevNextButtons();