Serenity Operating System
at master 255 lines 9.1 kB view raw
1/* 2 * Copyright (c) 2021, Cesar Torres <shortanemoia@protonmail.com> 3 * Copyright (c) 2021, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "SoundPlayerWidgetAdvancedView.h" 9#include "BarsVisualizationWidget.h" 10#include "M3UParser.h" 11#include "PlaybackManager.h" 12#include <AK/LexicalPath.h> 13#include <AK/SIMD.h> 14#include <LibGUI/Action.h> 15#include <LibGUI/BoxLayout.h> 16#include <LibGUI/Button.h> 17#include <LibGUI/Label.h> 18#include <LibGUI/MessageBox.h> 19#include <LibGUI/Slider.h> 20#include <LibGUI/Splitter.h> 21#include <LibGUI/Toolbar.h> 22#include <LibGUI/ToolbarContainer.h> 23#include <LibGUI/Window.h> 24#include <LibGfx/Bitmap.h> 25 26SoundPlayerWidgetAdvancedView::SoundPlayerWidgetAdvancedView(GUI::Window& window, Audio::ConnectionToServer& connection) 27 : Player(connection) 28 , m_window(window) 29{ 30 window.resize(455, 350); 31 window.set_resizable(true); 32 set_fill_with_background_color(true); 33 34 set_layout<GUI::VerticalBoxLayout>(); 35 m_splitter = add<GUI::HorizontalSplitter>(); 36 m_player_view = m_splitter->add<GUI::Widget>(); 37 38 m_playlist_widget = PlaylistWidget::construct(); 39 m_playlist_widget->set_data_model(playlist().model()); 40 m_playlist_widget->set_preferred_width(150); 41 42 m_player_view->set_layout<GUI::VerticalBoxLayout>(); 43 44 m_play_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/play.png"sv).release_value_but_fixme_should_propagate_errors(); 45 m_pause_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/pause.png"sv).release_value_but_fixme_should_propagate_errors(); 46 m_stop_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/stop.png"sv).release_value_but_fixme_should_propagate_errors(); 47 m_back_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/go-back.png"sv).release_value_but_fixme_should_propagate_errors(); 48 m_next_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"sv).release_value_but_fixme_should_propagate_errors(); 49 m_volume_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/audio-volume-medium.png"sv).release_value_but_fixme_should_propagate_errors(); 50 m_muted_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/audio-volume-muted.png"sv).release_value_but_fixme_should_propagate_errors(); 51 52 m_visualization = m_player_view->add<BarsVisualizationWidget>(); 53 54 m_playback_progress_slider = m_player_view->add<GUI::HorizontalSlider>(); 55 m_playback_progress_slider->set_fixed_height(20); 56 m_playback_progress_slider->set_jump_to_cursor(true); 57 m_playback_progress_slider->set_min(0); 58 m_playback_progress_slider->on_change = [&](int value) { 59 if (!m_playback_progress_slider->knob_dragging()) 60 seek(value); 61 }; 62 m_playback_progress_slider->on_drag_end = [&]() { 63 seek(m_playback_progress_slider->value()); 64 }; 65 66 auto& toolbar_container = m_player_view->add<GUI::ToolbarContainer>(); 67 auto& menubar = toolbar_container.add<GUI::Toolbar>(); 68 69 m_play_action = GUI::Action::create("Play", { Key_Space }, m_play_icon, [&](auto&) { 70 toggle_pause(); 71 }); 72 m_play_action->set_enabled(false); 73 menubar.add_action(*m_play_action); 74 75 m_stop_action = GUI::Action::create("Stop", { Key_S }, m_stop_icon, [&](auto&) { 76 stop(); 77 }); 78 m_stop_action->set_enabled(false); 79 menubar.add_action(*m_stop_action); 80 81 menubar.add_separator(); 82 83 m_timestamp_label = menubar.add<GUI::Label>(); 84 m_timestamp_label->set_fixed_width(110); 85 86 // Filler label 87 menubar.add<GUI::Label>(); 88 89 m_back_action = GUI::Action::create("Back", m_back_icon, [&](auto&) { 90 play_file_path(playlist().previous()); 91 }); 92 m_back_action->set_enabled(false); 93 menubar.add_action(*m_back_action); 94 95 m_next_action = GUI::Action::create("Next", m_next_icon, [&](auto&) { 96 play_file_path(playlist().next()); 97 }); 98 m_next_action->set_enabled(false); 99 menubar.add_action(*m_next_action); 100 101 menubar.add_separator(); 102 103 m_mute_action = GUI::Action::create("Mute", { Key_M }, m_volume_icon, [&](auto&) { 104 toggle_mute(); 105 }); 106 m_mute_action->set_enabled(true); 107 menubar.add_action(*m_mute_action); 108 109 m_volume_label = &menubar.add<GUI::Label>(); 110 m_volume_label->set_fixed_width(30); 111 112 m_volume_slider = &menubar.add<GUI::HorizontalSlider>(); 113 m_volume_slider->set_fixed_width(95); 114 m_volume_slider->set_min(0); 115 m_volume_slider->set_max(150); 116 m_volume_slider->set_value(100); 117 118 m_volume_slider->on_change = [&](int value) { 119 double volume = m_nonlinear_volume_slider ? (double)(value * value) / (100 * 100) : value / 100.; 120 set_volume(volume); 121 }; 122 123 set_nonlinear_volume_slider(false); 124 125 done_initializing(); 126} 127 128void SoundPlayerWidgetAdvancedView::set_nonlinear_volume_slider(bool nonlinear) 129{ 130 m_nonlinear_volume_slider = nonlinear; 131} 132 133void SoundPlayerWidgetAdvancedView::drag_enter_event(GUI::DragEvent& event) 134{ 135 auto const& mime_types = event.mime_types(); 136 if (mime_types.contains_slow("text/uri-list")) 137 event.accept(); 138} 139 140void SoundPlayerWidgetAdvancedView::drop_event(GUI::DropEvent& event) 141{ 142 event.accept(); 143 144 if (event.mime_data().has_urls()) { 145 auto urls = event.mime_data().urls(); 146 if (urls.is_empty()) 147 return; 148 window()->move_to_front(); 149 // FIXME: Add all paths from drop event to the playlist 150 play_file_path(urls.first().path()); 151 } 152} 153 154void SoundPlayerWidgetAdvancedView::keydown_event(GUI::KeyEvent& event) 155{ 156 if (event.key() == Key_Up) 157 m_volume_slider->increase_slider_by_page_steps(1); 158 159 if (event.key() == Key_Down) 160 m_volume_slider->decrease_slider_by_page_steps(1); 161 162 GUI::Widget::keydown_event(event); 163} 164 165void SoundPlayerWidgetAdvancedView::set_playlist_visible(bool visible) 166{ 167 if (!visible) { 168 m_playlist_widget->remove_from_parent(); 169 m_player_view->set_max_width(window()->width()); 170 } else if (!m_playlist_widget->parent()) { 171 m_player_view->parent_widget()->add_child(*m_playlist_widget); 172 } 173} 174 175void SoundPlayerWidgetAdvancedView::play_state_changed(Player::PlayState state) 176{ 177 sync_previous_next_actions(); 178 179 m_play_action->set_enabled(state != PlayState::NoFileLoaded); 180 m_play_action->set_icon(state == PlayState::Playing ? m_pause_icon : m_play_icon); 181 m_play_action->set_text(state == PlayState::Playing ? "Pause"sv : "Play"sv); 182 183 m_stop_action->set_enabled(state != PlayState::Stopped && state != PlayState::NoFileLoaded); 184 185 m_playback_progress_slider->set_enabled(state != PlayState::NoFileLoaded); 186} 187 188void SoundPlayerWidgetAdvancedView::loop_mode_changed(Player::LoopMode) 189{ 190} 191 192void SoundPlayerWidgetAdvancedView::mute_changed(bool muted) 193{ 194 m_mute_action->set_text(muted ? "Unmute"sv : "Mute"sv); 195 m_mute_action->set_icon(muted ? m_muted_icon : m_volume_icon); 196 m_volume_slider->set_enabled(!muted); 197} 198 199void SoundPlayerWidgetAdvancedView::sync_previous_next_actions() 200{ 201 m_back_action->set_enabled(playlist().size() > 1 && !playlist().shuffling()); 202 m_next_action->set_enabled(playlist().size() > 1); 203} 204 205void SoundPlayerWidgetAdvancedView::shuffle_mode_changed(Player::ShuffleMode) 206{ 207 sync_previous_next_actions(); 208} 209 210void SoundPlayerWidgetAdvancedView::time_elapsed(int seconds) 211{ 212 m_timestamp_label->set_text(DeprecatedString::formatted("Elapsed: {:02}:{:02}:{:02}", seconds / 3600, seconds / 60, seconds % 60)); 213} 214 215void SoundPlayerWidgetAdvancedView::file_name_changed(StringView name) 216{ 217 m_visualization->start_new_file(name); 218 m_window.set_title(DeprecatedString::formatted("{} - Sound Player", name)); 219} 220 221void SoundPlayerWidgetAdvancedView::total_samples_changed(int total_samples) 222{ 223 m_playback_progress_slider->set_max(total_samples); 224 m_playback_progress_slider->set_page_step(total_samples / 10); 225} 226 227void SoundPlayerWidgetAdvancedView::sound_buffer_played(FixedArray<Audio::Sample> const& buffer, int sample_rate, int samples_played) 228{ 229 m_visualization->set_buffer(buffer); 230 m_visualization->set_samplerate(sample_rate); 231 // If the user is currently dragging the slider, don't interfere. 232 if (!m_playback_progress_slider->knob_dragging()) 233 m_playback_progress_slider->set_value(samples_played, GUI::AllowCallback::No); 234} 235 236void SoundPlayerWidgetAdvancedView::volume_changed(double volume) 237{ 238 m_volume_label->set_text(DeprecatedString::formatted("{}%", static_cast<int>(volume * 100))); 239} 240 241void SoundPlayerWidgetAdvancedView::playlist_loaded(StringView path, bool loaded) 242{ 243 if (!loaded) { 244 GUI::MessageBox::show(&m_window, DeprecatedString::formatted("Could not load playlist at \"{}\".", path), "Error opening playlist"sv, GUI::MessageBox::Type::Error); 245 return; 246 } 247 set_playlist_visible(true); 248 play_file_path(playlist().next()); 249} 250 251void SoundPlayerWidgetAdvancedView::audio_load_error(StringView path, StringView error_string) 252{ 253 GUI::MessageBox::show(&m_window, DeprecatedString::formatted("Failed to load audio file: {} ({})", path, error_string.is_null() ? "Unknown error"sv : error_string), 254 "Filetype error"sv, GUI::MessageBox::Type::Error); 255}