mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 09:02:43 +00:00 
			
		
		
		
	AudioPlayer: Further decouple the player from the GUI
This commit is contained in:
		
							parent
							
								
									f9e4bff487
								
							
						
					
					
						commit
						e4d6a56a28
					
				
					 6 changed files with 136 additions and 76 deletions
				
			
		|  | @ -28,11 +28,48 @@ | |||
| 
 | ||||
| #include "PlaybackManager.h" | ||||
| #include "VisualizationBase.h" | ||||
| #include <AK/RefPtr.h> | ||||
| 
 | ||||
| struct PlayerState { | ||||
|     bool is_paused; | ||||
|     bool is_stopped; | ||||
|     bool has_loaded_file; | ||||
|     bool is_looping; | ||||
|     double volume; | ||||
|     Audio::ClientConnection& connection; | ||||
|     PlaybackManager& manager; | ||||
|     StringView loaded_filename; | ||||
| }; | ||||
| 
 | ||||
| class Player { | ||||
| public: | ||||
|     explicit Player() = default; | ||||
|     explicit Player(PlayerState& state) | ||||
|         : m_player_state(state) {}; | ||||
|     virtual void open_file(StringView path) = 0; | ||||
|     virtual Audio::ClientConnection& client_connection() = 0; | ||||
|     virtual PlaybackManager& playback_manager() = 0; | ||||
|     virtual void play() = 0; | ||||
| 
 | ||||
|     PlayerState& get_player_state() { return m_player_state; } | ||||
|     bool is_stopped() const { return m_player_state.is_stopped; } | ||||
|     bool is_paused() const { return m_player_state.is_paused; } | ||||
|     bool has_loaded_file() const { return m_player_state.has_loaded_file; } | ||||
|     double volume() const { return m_player_state.volume; } | ||||
|     bool looping() const { return m_player_state.is_looping; } | ||||
|     StringView& loaded_filename() { return m_player_state.loaded_filename; } | ||||
| 
 | ||||
|     virtual void set_stopped(bool stopped) { m_player_state.is_stopped = stopped; } | ||||
|     virtual void set_paused(bool paused) { m_player_state.is_paused = paused; } | ||||
|     virtual void set_has_loaded_file(bool loaded) { m_player_state.has_loaded_file = loaded; } | ||||
|     virtual void set_volume(double volume) { m_player_state.volume = volume; } | ||||
|     virtual void set_looping(bool loop) | ||||
|     { | ||||
|         m_player_state.is_looping = loop; | ||||
|         manager().loop(loop); | ||||
|     } | ||||
|     virtual void set_loaded_filename(StringView& filename) { m_player_state.loaded_filename = filename; } | ||||
| 
 | ||||
|     Audio::ClientConnection& client_connection() { return m_player_state.connection; } | ||||
|     PlaybackManager& manager() { return m_player_state.manager; } | ||||
| 
 | ||||
| protected: | ||||
|     PlayerState m_player_state; | ||||
| }; | ||||
|  |  | |||
|  | @ -33,10 +33,9 @@ | |||
| #include <LibGUI/Label.h> | ||||
| #include <LibGUI/MessageBox.h> | ||||
| 
 | ||||
| SoundPlayerWidget::SoundPlayerWidget(GUI::Window& window, Audio::ClientConnection& connection, PlaybackManager& manager) | ||||
|     : m_window(window) | ||||
|     , m_connection(connection) | ||||
|     , m_manager(manager) | ||||
| SoundPlayerWidget::SoundPlayerWidget(GUI::Window& window, PlayerState& state) | ||||
|     : Player(state) | ||||
|     , m_window(window) | ||||
| { | ||||
|     window.set_resizable(false); | ||||
|     window.resize(350, 140); | ||||
|  | @ -68,8 +67,8 @@ SoundPlayerWidget::SoundPlayerWidget(GUI::Window& window, Audio::ClientConnectio | |||
| 
 | ||||
|     m_slider = add<Slider>(Orientation::Horizontal); | ||||
|     m_slider->set_min(0); | ||||
|     m_slider->set_enabled(false); | ||||
|     m_slider->on_knob_released = [&](int value) { m_manager.seek(denormalize_rate(value)); }; | ||||
|     m_slider->set_enabled(has_loaded_file()); | ||||
|     m_slider->on_knob_released = [&](int value) { manager().seek(denormalize_rate(value)); }; | ||||
| 
 | ||||
|     auto& control_widget = add<GUI::Widget>(); | ||||
|     control_widget.set_fill_with_background_color(true); | ||||
|  | @ -79,16 +78,21 @@ SoundPlayerWidget::SoundPlayerWidget(GUI::Window& window, Audio::ClientConnectio | |||
|     control_widget.layout()->set_spacing(10); | ||||
| 
 | ||||
|     m_play = control_widget.add<GUI::Button>(); | ||||
|     m_play->set_icon(*m_pause_icon); | ||||
|     m_play->set_enabled(false); | ||||
|     m_play->set_icon(has_loaded_file() ? *m_play_icon : *m_pause_icon); | ||||
|     m_play->set_enabled(has_loaded_file()); | ||||
|     m_play->on_click = [this](auto) { | ||||
|         m_play->set_icon(m_manager.toggle_pause() ? *m_play_icon : *m_pause_icon); | ||||
|         bool paused = manager().toggle_pause(); | ||||
|         set_paused(paused); | ||||
|         m_play->set_icon(paused ? *m_play_icon : *m_pause_icon); | ||||
|     }; | ||||
| 
 | ||||
|     m_stop = control_widget.add<GUI::Button>(); | ||||
|     m_stop->set_enabled(false); | ||||
|     m_stop->set_enabled(has_loaded_file()); | ||||
|     m_stop->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/stop.png")); | ||||
|     m_stop->on_click = [this](auto) { m_manager.stop(); }; | ||||
|     m_stop->on_click = [this](auto) { | ||||
|         manager().stop(); | ||||
|         set_stopped(true); | ||||
|     }; | ||||
| 
 | ||||
|     m_status = add<GUI::Label>(); | ||||
|     m_status->set_frame_shape(Gfx::FrameShape::Box); | ||||
|  | @ -96,11 +100,11 @@ SoundPlayerWidget::SoundPlayerWidget(GUI::Window& window, Audio::ClientConnectio | |||
|     m_status->set_frame_thickness(4); | ||||
|     m_status->set_text_alignment(Gfx::TextAlignment::CenterLeft); | ||||
|     m_status->set_fixed_height(18); | ||||
|     m_status->set_text("No file open!"); | ||||
|     m_status->set_text(has_loaded_file() ? loaded_filename() : "No file open!"); | ||||
| 
 | ||||
|     update_position(0); | ||||
| 
 | ||||
|     m_manager.on_update = [&]() { update_ui(); }; | ||||
|     manager().on_update = [&]() { update_ui(); }; | ||||
| } | ||||
| 
 | ||||
| SoundPlayerWidget::~SoundPlayerWidget() | ||||
|  | @ -132,8 +136,10 @@ void SoundPlayerWidget::open_file(StringView path) | |||
|         loader->num_channels(), | ||||
|         loader->bits_per_sample())); | ||||
| 
 | ||||
|     m_manager.set_loader(move(loader)); | ||||
|     manager().set_loader(move(loader)); | ||||
|     update_position(0); | ||||
|     set_has_loaded_file(true); | ||||
|     set_loaded_filename(path); | ||||
| } | ||||
| 
 | ||||
| void SoundPlayerWidget::drop_event(GUI::DropEvent& event) | ||||
|  | @ -161,16 +167,16 @@ int SoundPlayerWidget::denormalize_rate(int rate) const | |||
| 
 | ||||
| void SoundPlayerWidget::update_ui() | ||||
| { | ||||
|     m_sample_widget->set_buffer(m_manager.current_buffer()); | ||||
|     m_play->set_icon(m_manager.is_paused() ? *m_play_icon : *m_pause_icon); | ||||
|     update_position(m_manager.connection()->get_played_samples()); | ||||
|     m_sample_widget->set_buffer(manager().current_buffer()); | ||||
|     m_play->set_icon(manager().is_paused() ? *m_play_icon : *m_pause_icon); | ||||
|     update_position(manager().connection()->get_played_samples()); | ||||
| } | ||||
| 
 | ||||
| void SoundPlayerWidget::update_position(const int position) | ||||
| { | ||||
|     int total_norm_samples = position + normalize_rate(m_manager.last_seek()); | ||||
|     int total_norm_samples = position + normalize_rate(manager().last_seek()); | ||||
|     float seconds = (total_norm_samples / static_cast<float>(PLAYBACK_MANAGER_RATE)); | ||||
|     float remaining_seconds = m_manager.total_length() - seconds; | ||||
|     float remaining_seconds = manager().total_length() - seconds; | ||||
| 
 | ||||
|     m_elapsed->set_text(String::formatted( | ||||
|         "Elapsed:\n{}:{:02}.{:02}", | ||||
|  | @ -191,3 +197,10 @@ void SoundPlayerWidget::hide_scope(bool hide) | |||
| { | ||||
|     m_sample_widget->set_visible(!hide); | ||||
| } | ||||
| 
 | ||||
| void SoundPlayerWidget::play() | ||||
| { | ||||
|     manager().play(); | ||||
|     set_paused(false); | ||||
|     set_stopped(false); | ||||
| } | ||||
|  |  | |||
|  | @ -40,16 +40,15 @@ class SoundPlayerWidget final : public GUI::Widget | |||
|     , public Player { | ||||
|     C_OBJECT(SoundPlayerWidget) | ||||
| public: | ||||
|     virtual ~SoundPlayerWidget() override; | ||||
|     ~SoundPlayerWidget() override; | ||||
|     void open_file(StringView path) override; | ||||
|     void play() override; | ||||
|     void hide_scope(bool); | ||||
|     Audio::ClientConnection& client_connection() override { return m_connection; } | ||||
|     PlaybackManager& playback_manager() override { return m_manager; } | ||||
| 
 | ||||
| private: | ||||
|     explicit SoundPlayerWidget(GUI::Window& window, Audio::ClientConnection& connection, PlaybackManager& manager); | ||||
|     explicit SoundPlayerWidget(GUI::Window& window, PlayerState& state); | ||||
| 
 | ||||
|     virtual void drop_event(GUI::DropEvent&) override; | ||||
|     void drop_event(GUI::DropEvent&) override; | ||||
| 
 | ||||
|     void update_position(const int position); | ||||
|     void update_ui(); | ||||
|  | @ -57,8 +56,6 @@ private: | |||
|     int denormalize_rate(int) const; | ||||
| 
 | ||||
|     GUI::Window& m_window; | ||||
|     Audio::ClientConnection& m_connection; | ||||
|     PlaybackManager& m_manager; | ||||
| 
 | ||||
|     float m_sample_ratio { 1.0 }; | ||||
|     RefPtr<GUI::Label> m_status; | ||||
|  |  | |||
|  | @ -41,12 +41,10 @@ | |||
| #include <LibGUI/Window.h> | ||||
| #include <LibGfx/Bitmap.h> | ||||
| 
 | ||||
| SoundPlayerWidgetAdvancedView::SoundPlayerWidgetAdvancedView(GUI::Window& window, Audio::ClientConnection& connection, PlaybackManager& manager) | ||||
|     : m_window(window) | ||||
|     , m_connection(connection) | ||||
|     , m_manager(manager) | ||||
| SoundPlayerWidgetAdvancedView::SoundPlayerWidgetAdvancedView(GUI::Window& window, PlayerState& state) | ||||
|     : Player(state) | ||||
|     , m_window(window) | ||||
| { | ||||
| 
 | ||||
|     window.resize(455, 350); | ||||
|     window.set_minimum_size(440, 130); | ||||
|     window.set_resizable(true); | ||||
|  | @ -65,9 +63,9 @@ SoundPlayerWidgetAdvancedView::SoundPlayerWidgetAdvancedView(GUI::Window& window | |||
|     m_playback_progress_slider = add<Slider>(Orientation::Horizontal); | ||||
|     m_playback_progress_slider->set_fixed_height(20); | ||||
|     m_playback_progress_slider->set_min(0); | ||||
|     m_playback_progress_slider->set_max(m_manager.total_length() * 44100); //this value should be set when we load a new file
 | ||||
|     m_playback_progress_slider->set_max(this->manager().total_length() * 44100); //this value should be set when we load a new file
 | ||||
|     m_playback_progress_slider->on_knob_released = [&](int value) { | ||||
|         m_manager.seek(value); | ||||
|         this->manager().seek(value); | ||||
|     }; | ||||
| 
 | ||||
|     auto& toolbar_container = add<GUI::ToolBarContainer>(); | ||||
|  | @ -75,20 +73,22 @@ SoundPlayerWidgetAdvancedView::SoundPlayerWidgetAdvancedView(GUI::Window& window | |||
|     auto& menubar = toolbar_container.add<GUI::ToolBar>(); | ||||
| 
 | ||||
|     m_play_button = menubar.add<GUI::Button>(); | ||||
|     m_play_button->set_icon(*m_play_icon); | ||||
|     m_play_button->set_icon(is_paused() ? (!has_loaded_file() ? *m_play_icon : *m_pause_icon) : *m_pause_icon); | ||||
|     m_play_button->set_fixed_width(50); | ||||
| 
 | ||||
|     m_play_button->set_enabled(has_loaded_file()); | ||||
|     m_play_button->on_click = [&](unsigned) { | ||||
|         bool paused = m_manager.toggle_pause(); | ||||
|         bool paused = this->manager().toggle_pause(); | ||||
|         set_paused(paused); | ||||
|         m_play_button->set_icon(paused ? *m_play_icon : *m_pause_icon); | ||||
|         m_stop_button->set_enabled(!paused); | ||||
|     }; | ||||
| 
 | ||||
|     m_stop_button = menubar.add<GUI::Button>(); | ||||
|     m_stop_button->set_icon(*m_stop_icon); | ||||
|     m_stop_button->set_fixed_width(50); | ||||
|     m_stop_button->set_enabled(has_loaded_file()); | ||||
|     m_stop_button->on_click = [&](unsigned) { | ||||
|         m_manager.stop(); | ||||
|         this->manager().stop(); | ||||
|         set_stopped(true); | ||||
|         m_play_button->set_icon(*m_play_icon); | ||||
|         m_stop_button->set_enabled(false); | ||||
|     }; | ||||
|  | @ -100,13 +100,15 @@ SoundPlayerWidgetAdvancedView::SoundPlayerWidgetAdvancedView(GUI::Window& window | |||
|     // filler_label
 | ||||
|     menubar.add<GUI::Label>(); | ||||
| 
 | ||||
|     auto& back_button = menubar.add<GUI::Button>(); | ||||
|     back_button.set_fixed_width(50); | ||||
|     back_button.set_icon(*m_back_icon); | ||||
|     m_back_button = menubar.add<GUI::Button>(); | ||||
|     m_back_button->set_fixed_width(50); | ||||
|     m_back_button->set_icon(*m_back_icon); | ||||
|     m_back_button->set_enabled(has_loaded_file()); | ||||
| 
 | ||||
|     auto& next_button = menubar.add<GUI::Button>(); | ||||
|     next_button.set_fixed_width(50); | ||||
|     next_button.set_icon(*m_next_icon); | ||||
|     m_next_button = menubar.add<GUI::Button>(); | ||||
|     m_next_button->set_fixed_width(50); | ||||
|     m_next_button->set_icon(*m_next_icon); | ||||
|     m_next_button->set_enabled(has_loaded_file()); | ||||
| 
 | ||||
|     m_volume_label = &menubar.add<GUI::Label>(); | ||||
|     m_volume_label->set_fixed_width(30); | ||||
|  | @ -127,41 +129,36 @@ SoundPlayerWidgetAdvancedView::SoundPlayerWidgetAdvancedView(GUI::Window& window | |||
|     set_volume(1.); | ||||
|     set_nonlinear_volume_slider(false); | ||||
| 
 | ||||
|     m_manager.on_update = [&]() { | ||||
|     manager().on_update = [&]() { | ||||
|         //TODO: make this program support other sample rates
 | ||||
|         int samples_played = m_connection.get_played_samples() + m_manager.last_seek(); | ||||
|         int samples_played = client_connection().get_played_samples() + this->manager().last_seek(); | ||||
|         int current_second = samples_played / 44100; | ||||
|         timestamp_label.set_text(String::formatted("Elapsed: {:02}:{:02}:{:02}", current_second / 3600, current_second / 60, current_second % 60)); | ||||
|         m_playback_progress_slider->set_value(samples_played); | ||||
| 
 | ||||
|         dynamic_cast<Visualization*>(m_visualization.ptr())->set_buffer(m_manager.current_buffer()); | ||||
|         dynamic_cast<Visualization*>(m_visualization.ptr())->set_buffer(this->manager().current_buffer()); | ||||
|     }; | ||||
| 
 | ||||
|     m_manager.on_load_sample_buffer = [&](Audio::Buffer& buffer) { | ||||
|         if (m_volume == 1.) | ||||
|     this->manager().on_load_sample_buffer = [&](Audio::Buffer& buffer) { | ||||
|         if (volume() == 1.) | ||||
|             return; | ||||
|         auto sample_count = buffer.sample_count(); | ||||
|         if (sample_count % 4 == 0) { | ||||
|             const int total_iter = sample_count / (sizeof(AK::SIMD::f64x4) / sizeof(double) / 2); | ||||
|             AK::SIMD::f64x4* sample_ptr = const_cast<AK::SIMD::f64x4*>(reinterpret_cast<const AK::SIMD::f64x4*>((buffer.data()))); | ||||
|             for (int i = 0; i < total_iter; ++i) { | ||||
|                 sample_ptr[i] = sample_ptr[i] * m_volume; | ||||
|                 sample_ptr[i] = sample_ptr[i] * volume(); | ||||
|             } | ||||
|         } else { | ||||
|             const int total_iter = sample_count / (sizeof(AK::SIMD::f64x2) / sizeof(double) / 2); | ||||
|             AK::SIMD::f64x2* sample_ptr = const_cast<AK::SIMD::f64x2*>(reinterpret_cast<const AK::SIMD::f64x2*>((buffer.data()))); | ||||
|             for (int i = 0; i < total_iter; ++i) { | ||||
|                 sample_ptr[i] = sample_ptr[i] * m_volume; | ||||
|                 sample_ptr[i] = sample_ptr[i] * volume(); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| void SoundPlayerWidgetAdvancedView::set_volume(double value) | ||||
| { | ||||
|     m_volume = value; | ||||
| } | ||||
| 
 | ||||
| void SoundPlayerWidgetAdvancedView::open_file(StringView path) | ||||
| { | ||||
|     NonnullRefPtr<Audio::Loader> loader = Audio::Loader::create(path); | ||||
|  | @ -173,7 +170,12 @@ void SoundPlayerWidgetAdvancedView::open_file(StringView path) | |||
|     } | ||||
|     m_window.set_title(String::formatted("{} - SoundPlayer", loader->file()->filename())); | ||||
|     m_playback_progress_slider->set_max(loader->total_samples()); | ||||
|     m_manager.set_loader(move(loader)); | ||||
|     m_playback_progress_slider->set_enabled(true); | ||||
|     m_play_button->set_enabled(true); | ||||
|     m_stop_button->set_enabled(true); | ||||
|     manager().set_loader(move(loader)); | ||||
|     set_has_loaded_file(true); | ||||
|     set_loaded_filename(path); | ||||
| } | ||||
| 
 | ||||
| void SoundPlayerWidgetAdvancedView::set_nonlinear_volume_slider(bool nonlinear) | ||||
|  | @ -196,5 +198,13 @@ void SoundPlayerWidgetAdvancedView::drop_event(GUI::DropEvent& event) | |||
| 
 | ||||
| SoundPlayerWidgetAdvancedView::~SoundPlayerWidgetAdvancedView() | ||||
| { | ||||
|     m_manager.on_load_sample_buffer = nullptr; | ||||
|     manager().on_load_sample_buffer = nullptr; | ||||
|     manager().on_update = nullptr; | ||||
| } | ||||
| 
 | ||||
| void SoundPlayerWidgetAdvancedView::play() | ||||
| { | ||||
|     manager().play(); | ||||
|     set_paused(false); | ||||
|     set_stopped(false); | ||||
| } | ||||
|  |  | |||
|  | @ -39,12 +39,11 @@ class SoundPlayerWidgetAdvancedView final : public GUI::Widget | |||
|     C_OBJECT(SoundPlayerWidgetAdvancedView) | ||||
| 
 | ||||
| public: | ||||
|     explicit SoundPlayerWidgetAdvancedView(GUI::Window& window, Audio::ClientConnection& connection, PlaybackManager& manager); | ||||
|     explicit SoundPlayerWidgetAdvancedView(GUI::Window& window, PlayerState& state); | ||||
|     ~SoundPlayerWidgetAdvancedView() override; | ||||
| 
 | ||||
|     void open_file(StringView path) override; | ||||
|     Audio::ClientConnection& client_connection() override { return m_connection; } | ||||
|     PlaybackManager& playback_manager() override { return m_manager; } | ||||
|     void play() override; | ||||
| 
 | ||||
|     template<typename T> | ||||
|     void set_visualization() | ||||
|  | @ -58,14 +57,9 @@ public: | |||
| 
 | ||||
|     void set_nonlinear_volume_slider(bool nonlinear); | ||||
| 
 | ||||
|     void set_volume(double value); | ||||
| 
 | ||||
| private: | ||||
|     void drop_event(GUI::DropEvent& event) override; | ||||
| 
 | ||||
|     GUI::Window& m_window; | ||||
|     Audio::ClientConnection& m_connection; | ||||
|     PlaybackManager& m_manager; | ||||
| 
 | ||||
|     RefPtr<GUI::Widget> m_visualization; | ||||
| 
 | ||||
|  | @ -77,9 +71,10 @@ private: | |||
| 
 | ||||
|     RefPtr<GUI::Button> m_play_button; | ||||
|     RefPtr<GUI::Button> m_stop_button; | ||||
|     RefPtr<GUI::Button> m_back_button; | ||||
|     RefPtr<GUI::Button> m_next_button; | ||||
|     RefPtr<Slider> m_playback_progress_slider; | ||||
|     RefPtr<GUI::Label> m_volume_label; | ||||
| 
 | ||||
|     double m_volume; | ||||
|     bool m_nonlinear_volume_slider; | ||||
| }; | ||||
|  |  | |||
|  | @ -54,8 +54,15 @@ int main(int argc, char** argv) | |||
| 
 | ||||
|     auto audio_client = Audio::ClientConnection::construct(); | ||||
|     audio_client->handshake(); | ||||
| 
 | ||||
|     PlaybackManager playback_manager(audio_client); | ||||
|     PlayerState initial_player_state { true, | ||||
|         true, | ||||
|         false, | ||||
|         false, | ||||
|         1.0, | ||||
|         audio_client, | ||||
|         playback_manager, | ||||
|         "" }; | ||||
| 
 | ||||
|     if (pledge("stdio recvfd sendfd accept rpath thread", nullptr) < 0) { | ||||
|         perror("pledge"); | ||||
|  | @ -72,11 +79,11 @@ int main(int argc, char** argv) | |||
| 
 | ||||
|     auto& app_menu = menubar->add_menu("File"); | ||||
|     // start in simple view by default
 | ||||
|     Player* player = &window->set_main_widget<SoundPlayerWidget>(window, audio_client, playback_manager); | ||||
|     Player* player = &window->set_main_widget<SoundPlayerWidget>(window, initial_player_state); | ||||
|     if (argc > 1) { | ||||
|         String path = argv[1]; | ||||
|         player->open_file(path); | ||||
|         player->playback_manager().play(); | ||||
|         player->play(); | ||||
|     } | ||||
| 
 | ||||
|     app_menu.add_action(GUI::CommonActions::make_open_action([&](auto&) { | ||||
|  | @ -89,12 +96,13 @@ int main(int argc, char** argv) | |||
|     RefPtr<GUI::Action> hide_scope; | ||||
| 
 | ||||
|     auto advanced_view_check = GUI::Action::create_checkable("Advanced view", { Mod_Ctrl, Key_A }, [&](auto& action) { | ||||
|         PlayerState state = player->get_player_state(); | ||||
|         window->close(); | ||||
|         if (action.is_checked()) { | ||||
|             player = &window->set_main_widget<SoundPlayerWidgetAdvancedView>(window, audio_client, playback_manager); | ||||
|             player = &window->set_main_widget<SoundPlayerWidgetAdvancedView>(window, state); | ||||
|             hide_scope->set_checkable(false); | ||||
|         } else { | ||||
|             player = &window->set_main_widget<SoundPlayerWidget>(window, audio_client, playback_manager); | ||||
|             player = &window->set_main_widget<SoundPlayerWidget>(window, state); | ||||
|             hide_scope->set_checkable(true); | ||||
|         } | ||||
|         window->show(); | ||||
|  | @ -123,7 +131,7 @@ int main(int argc, char** argv) | |||
|     auto& playback_menu = menubar->add_menu("Playback"); | ||||
| 
 | ||||
|     auto loop = GUI::Action::create_checkable("Loop", { Mod_Ctrl, Key_R }, [&](auto& action) { | ||||
|         player->playback_manager().loop(action.is_checked()); | ||||
|         player->set_looping(action.is_checked()); | ||||
|     }); | ||||
| 
 | ||||
|     playback_menu.add_action(move(loop)); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Cesar Torres
						Cesar Torres