/* * Copyright (c) 2021, Cesar Torres * Copyright (c) 2021, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #include "SoundPlayerWidgetAdvancedView.h" #include "AlbumCoverVisualizationWidget.h" #include "BarsVisualizationWidget.h" #include "M3UParser.h" #include "PlaybackManager.h" #include "SampleWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SoundPlayerWidgetAdvancedView::SoundPlayerWidgetAdvancedView(GUI::Window& window, Audio::ConnectionToServer& connection, ImageDecoderClient::Client& image_decoder_client) : Player(connection) , m_window(window) , m_image_decoder_client(image_decoder_client) { window.resize(455, 350); window.set_resizable(true); set_fill_with_background_color(true); set_layout(); m_splitter = add(); m_player_view = m_splitter->add(); m_playlist_widget = PlaylistWidget::construct(); m_playlist_widget->set_data_model(playlist().model()); m_playlist_widget->set_preferred_width(150); m_player_view->set_layout(); m_play_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/play.png"sv).release_value_but_fixme_should_propagate_errors(); m_pause_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/pause.png"sv).release_value_but_fixme_should_propagate_errors(); m_stop_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/stop.png"sv).release_value_but_fixme_should_propagate_errors(); m_back_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/go-back.png"sv).release_value_but_fixme_should_propagate_errors(); m_next_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"sv).release_value_but_fixme_should_propagate_errors(); m_volume_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/audio-volume-medium.png"sv).release_value_but_fixme_should_propagate_errors(); m_muted_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/audio-volume-muted.png"sv).release_value_but_fixme_should_propagate_errors(); auto visualization = Config::read_string("SoundPlayer"sv, "Preferences"sv, "Visualization"sv, "bars"sv); if (visualization == "samples") { m_visualization = m_player_view->add(); } else if (visualization == "album_cover") { m_visualization = m_player_view->add([this]() { return get_image_from_music_file(); }); } else { m_visualization = m_player_view->add(); } m_playback_progress_slider = m_player_view->add(); m_playback_progress_slider->set_fixed_height(20); m_playback_progress_slider->set_jump_to_cursor(true); m_playback_progress_slider->set_min(0); m_playback_progress_slider->on_change = [&](int value) { if (!m_playback_progress_slider->knob_dragging()) seek(value); }; m_playback_progress_slider->on_drag_end = [&]() { seek(m_playback_progress_slider->value()); }; auto& toolbar_container = m_player_view->add(); auto& menubar = toolbar_container.add(); m_play_action = GUI::Action::create("Play", { Key_Space }, m_play_icon, [&](auto&) { toggle_pause(); }); m_play_action->set_enabled(false); menubar.add_action(*m_play_action); m_stop_action = GUI::Action::create("Stop", { Key_S }, m_stop_icon, [&](auto&) { stop(); }); m_stop_action->set_enabled(false); menubar.add_action(*m_stop_action); menubar.add_separator(); m_timestamp_label = menubar.add(); m_timestamp_label->set_fixed_width(110); // Filler label menubar.add(); m_back_action = GUI::Action::create("Back", m_back_icon, [&](auto&) { play_file_path(playlist().previous()); }); m_back_action->set_enabled(false); menubar.add_action(*m_back_action); m_next_action = GUI::Action::create("Next", m_next_icon, [&](auto&) { play_file_path(playlist().next()); }); m_next_action->set_enabled(false); menubar.add_action(*m_next_action); menubar.add_separator(); m_mute_action = GUI::Action::create("Mute", { Key_M }, m_volume_icon, [&](auto&) { toggle_mute(); }); m_mute_action->set_enabled(true); menubar.add_action(*m_mute_action); m_volume_label = &menubar.add(); m_volume_label->set_fixed_width(30); m_volume_slider = &menubar.add(); m_volume_slider->set_fixed_width(95); m_volume_slider->set_min(0); m_volume_slider->set_max(150); m_volume_slider->set_value(100); m_volume_slider->on_change = [&](int value) { double volume = m_nonlinear_volume_slider ? (double)(value * value) / (100 * 100) : value / 100.; set_volume(volume); }; set_nonlinear_volume_slider(false); done_initializing(); } void SoundPlayerWidgetAdvancedView::set_nonlinear_volume_slider(bool nonlinear) { m_nonlinear_volume_slider = nonlinear; } void SoundPlayerWidgetAdvancedView::drag_enter_event(GUI::DragEvent& event) { auto const& mime_types = event.mime_types(); if (mime_types.contains_slow("text/uri-list")) event.accept(); } void SoundPlayerWidgetAdvancedView::drop_event(GUI::DropEvent& event) { event.accept(); if (event.mime_data().has_urls()) { auto urls = event.mime_data().urls(); if (urls.is_empty()) return; window()->move_to_front(); // FIXME: Add all paths from drop event to the playlist play_file_path(urls.first().serialize_path()); } } void SoundPlayerWidgetAdvancedView::keydown_event(GUI::KeyEvent& event) { if (event.key() == Key_Up) m_volume_slider->increase_slider_by_page_steps(1); if (event.key() == Key_Down) m_volume_slider->decrease_slider_by_page_steps(1); GUI::Widget::keydown_event(event); } void SoundPlayerWidgetAdvancedView::set_playlist_visible(bool visible) { if (!visible) { m_playlist_widget->remove_from_parent(); m_player_view->set_max_width(window()->width()); } else if (!m_playlist_widget->parent()) { m_player_view->parent_widget()->add_child(*m_playlist_widget); } } RefPtr SoundPlayerWidgetAdvancedView::get_image_from_music_file() { auto const& pictures = this->pictures(); if (pictures.is_empty()) return {}; // FIXME: We randomly select the first picture available for the track, // We might want to hardcode or let the user set a preference. auto decoded_image_or_error = m_image_decoder_client.decode_image(pictures[0].data); if (!decoded_image_or_error.has_value()) return {}; auto const decoded_image = decoded_image_or_error.release_value(); return decoded_image.frames[0].bitmap; } void SoundPlayerWidgetAdvancedView::play_state_changed(Player::PlayState state) { sync_previous_next_actions(); m_play_action->set_enabled(state != PlayState::NoFileLoaded); m_play_action->set_icon(state == PlayState::Playing ? m_pause_icon : m_play_icon); m_play_action->set_text(state == PlayState::Playing ? "Pause"sv : "Play"sv); m_stop_action->set_enabled(state != PlayState::Stopped && state != PlayState::NoFileLoaded); m_playback_progress_slider->set_enabled(state != PlayState::NoFileLoaded); } void SoundPlayerWidgetAdvancedView::loop_mode_changed(Player::LoopMode) { } void SoundPlayerWidgetAdvancedView::mute_changed(bool muted) { m_mute_action->set_text(muted ? "Unmute"sv : "Mute"sv); m_mute_action->set_icon(muted ? m_muted_icon : m_volume_icon); m_volume_slider->set_enabled(!muted); } void SoundPlayerWidgetAdvancedView::sync_previous_next_actions() { m_back_action->set_enabled(playlist().size() > 1 && !playlist().shuffling()); m_next_action->set_enabled(playlist().size() > 1); } void SoundPlayerWidgetAdvancedView::shuffle_mode_changed(Player::ShuffleMode) { sync_previous_next_actions(); } void SoundPlayerWidgetAdvancedView::time_elapsed(int seconds) { m_timestamp_label->set_text(DeprecatedString::formatted("Elapsed: {:02}:{:02}:{:02}", seconds / 3600, seconds / 60, seconds % 60)); } void SoundPlayerWidgetAdvancedView::file_name_changed(StringView name) { m_visualization->start_new_file(name); DeprecatedString title = name; if (playback_manager().loader()) { auto const& metadata = playback_manager().loader()->metadata(); if (auto artists_or_error = metadata.all_artists(" / "_short_string); !artists_or_error.is_error() && artists_or_error.value().has_value() && metadata.title.has_value()) { title = DeprecatedString::formatted("{} – {}", metadata.title.value(), artists_or_error.release_value().release_value()); } else if (metadata.title.has_value()) { title = metadata.title.value().to_deprecated_string(); } } m_window.set_title(DeprecatedString::formatted("{} — Sound Player", title)); } void SoundPlayerWidgetAdvancedView::total_samples_changed(int total_samples) { m_playback_progress_slider->set_max(total_samples); m_playback_progress_slider->set_page_step(total_samples / 10); } void SoundPlayerWidgetAdvancedView::sound_buffer_played(FixedArray const& buffer, int sample_rate, int samples_played) { m_visualization->set_buffer(buffer); m_visualization->set_samplerate(sample_rate); // If the user is currently dragging the slider, don't interfere. if (!m_playback_progress_slider->knob_dragging()) m_playback_progress_slider->set_value(samples_played, GUI::AllowCallback::No); } void SoundPlayerWidgetAdvancedView::volume_changed(double volume) { m_volume_label->set_text(DeprecatedString::formatted("{}%", static_cast(volume * 100))); } void SoundPlayerWidgetAdvancedView::playlist_loaded(StringView path, bool loaded) { if (!loaded) { GUI::MessageBox::show(&m_window, DeprecatedString::formatted("Could not load playlist at \"{}\".", path), "Error opening playlist"sv, GUI::MessageBox::Type::Error); return; } set_playlist_visible(true); play_file_path(playlist().next()); } void SoundPlayerWidgetAdvancedView::audio_load_error(StringView path, StringView error_string) { GUI::MessageBox::show(&m_window, DeprecatedString::formatted("Failed to load audio file: {} ({})", path, error_string.is_null() ? "Unknown error"sv : error_string), "Filetype error"sv, GUI::MessageBox::Type::Error); }