ladybird/Userland/Applications/SoundPlayer/SoundPlayerWidgetAdvancedView.cpp
MacDue 35612c6a7f AK+Everywhere: Change URL::path() to serialize_path()
This now defaults to serializing the path with percent decoded segments
(which is what all callers expect), but has an option not to. This fixes
`file://` URLs with spaces in their paths.

The name has been changed to serialize_path() path to make it more clear
that this method will generate a new string each call (except for the
cannot_be_a_base_url() case). A few callers have then been updated to
avoid repeatedly calling this function.
2023-04-15 06:37:04 +02:00

295 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2021, Cesar Torres <shortanemoia@protonmail.com>
* 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 <AK/DeprecatedString.h>
#include <AK/LexicalPath.h>
#include <AK/SIMD.h>
#include <LibConfig/Client.h>
#include <LibGUI/Action.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/Label.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/Slider.h>
#include <LibGUI/Splitter.h>
#include <LibGUI/Toolbar.h>
#include <LibGUI/ToolbarContainer.h>
#include <LibGUI/Window.h>
#include <LibGfx/Bitmap.h>
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<GUI::VerticalBoxLayout>();
m_splitter = add<GUI::HorizontalSplitter>();
m_player_view = m_splitter->add<GUI::Widget>();
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<GUI::VerticalBoxLayout>();
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<SampleWidget>();
} else if (visualization == "album_cover") {
m_visualization = m_player_view->add<AlbumCoverVisualizationWidget>([this]() {
return get_image_from_music_file();
});
} else {
m_visualization = m_player_view->add<BarsVisualizationWidget>();
}
m_playback_progress_slider = m_player_view->add<GUI::HorizontalSlider>();
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<GUI::ToolbarContainer>();
auto& menubar = toolbar_container.add<GUI::Toolbar>();
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<GUI::Label>();
m_timestamp_label->set_fixed_width(110);
// Filler label
menubar.add<GUI::Label>();
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<GUI::Label>();
m_volume_label->set_fixed_width(30);
m_volume_slider = &menubar.add<GUI::HorizontalSlider>();
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<Gfx::Bitmap> 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<Audio::Sample> 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<int>(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);
}