mirror of
https://github.com/fergalmoran/ladybird.git
synced 2026-03-25 06:45:28 +00:00
Use this handy AK function instead of reimplementing the formatting code several times. The one minor difference is that now, hours are only shown if the duration is at least an hour long, but that seems like an improvement to me. :^)
301 lines
11 KiB
C++
301 lines
11 KiB
C++
/*
|
||
* 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/NumberFormat.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);
|
||
if (state == PlayState::Stopped) {
|
||
m_playback_progress_slider->set_value(m_playback_progress_slider->min(), GUI::AllowCallback::No);
|
||
m_visualization->reset_buffer();
|
||
}
|
||
}
|
||
|
||
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(String::formatted("Elapsed: {}", human_readable_digital_time(seconds)).release_value_but_fixme_should_propagate_errors());
|
||
}
|
||
|
||
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(String::formatted("{}%", static_cast<int>(volume * 100)).release_value_but_fixme_should_propagate_errors());
|
||
}
|
||
|
||
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);
|
||
}
|