2021-03-21 09:01:33 -03:00
/*
* Copyright ( c ) 2021 , Cesar Torres < shortanemoia @ protonmail . com >
2021-09-02 09:23:28 -03:00
* Copyright ( c ) 2021 , the SerenityOS developers .
2021-03-21 09:01:33 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2021-03-21 09:01:33 -03:00
*/
2023-07-19 10:33:06 -03:00
# include "SoundPlayerWidget.h"
2023-03-19 13:36:51 -03:00
# include "AlbumCoverVisualizationWidget.h"
2021-03-21 09:01:33 -03:00
# include "BarsVisualizationWidget.h"
2021-03-25 21:26:12 -03:00
# include "M3UParser.h"
2021-03-21 09:01:33 -03:00
# include "PlaybackManager.h"
2023-03-19 13:36:51 -03:00
# include "SampleWidget.h"
2023-03-07 13:32:05 -03:00
# include <AK/DeprecatedString.h>
2021-03-25 21:26:12 -03:00
# include <AK/LexicalPath.h>
2023-07-19 07:05:37 -03:00
# include <AK/NumberFormat.h>
2021-03-21 09:01:33 -03:00
# include <AK/SIMD.h>
2023-03-19 14:00:55 -03:00
# include <LibConfig/Client.h>
2021-03-21 09:01:33 -03:00
# include <LibGUI/Action.h>
# include <LibGUI/BoxLayout.h>
# include <LibGUI/Button.h>
# include <LibGUI/Label.h>
# include <LibGUI/MessageBox.h>
# include <LibGUI/Slider.h>
2021-03-25 21:28:56 -03:00
# include <LibGUI/Splitter.h>
2021-04-13 11:18:20 -03:00
# include <LibGUI/Toolbar.h>
# include <LibGUI/ToolbarContainer.h>
2021-03-21 09:01:33 -03:00
# include <LibGUI/Window.h>
# include <LibGfx/Bitmap.h>
2023-07-19 10:33:06 -03:00
SoundPlayerWidget : : SoundPlayerWidget ( GUI : : Window & window , Audio : : ConnectionToServer & connection , ImageDecoderClient : : Client & image_decoder_client )
2021-09-30 01:55:42 -03:00
: Player ( connection )
2021-03-23 20:12:50 -03:00
, m_window ( window )
2023-03-19 13:36:51 -03:00
, m_image_decoder_client ( image_decoder_client )
2021-03-21 09:01:33 -03:00
{
window . resize ( 455 , 350 ) ;
window . set_resizable ( true ) ;
set_fill_with_background_color ( true ) ;
2021-03-25 21:26:12 -03:00
2021-03-21 09:01:33 -03:00
set_layout < GUI : : VerticalBoxLayout > ( ) ;
2021-03-25 21:26:12 -03:00
m_splitter = add < GUI : : HorizontalSplitter > ( ) ;
m_player_view = m_splitter - > add < GUI : : Widget > ( ) ;
2021-09-04 07:58:50 -03:00
m_playlist_widget = PlaylistWidget : : construct ( ) ;
2021-09-30 01:55:42 -03:00
m_playlist_widget - > set_data_model ( playlist ( ) . model ( ) ) ;
2022-08-30 08:38:20 -03:00
m_playlist_widget - > set_preferred_width ( 150 ) ;
2021-09-04 07:58:50 -03:00
2021-03-25 21:26:12 -03:00
m_player_view - > set_layout < GUI : : VerticalBoxLayout > ( ) ;
2021-03-21 09:01:33 -03:00
2023-01-20 16:06:05 -03:00
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 ( ) ;
2023-02-28 18:52:43 -03:00
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 ( ) ;
2021-03-21 09:01:33 -03:00
2023-03-19 14:00:55 -03:00
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 > ( ) ;
}
2021-03-21 09:01:33 -03:00
2023-02-05 22:20:03 -03:00
m_playback_progress_slider = m_player_view - > add < GUI : : HorizontalSlider > ( ) ;
2021-03-21 09:01:33 -03:00
m_playback_progress_slider - > set_fixed_height ( 20 ) ;
2021-06-02 21:42:08 -03:00
m_playback_progress_slider - > set_jump_to_cursor ( true ) ;
2021-03-21 09:01:33 -03:00
m_playback_progress_slider - > set_min ( 0 ) ;
2023-02-05 22:20:03 -03:00
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 ( ) ) ;
2021-03-21 09:01:33 -03:00
} ;
2021-04-13 11:18:20 -03:00
auto & toolbar_container = m_player_view - > add < GUI : : ToolbarContainer > ( ) ;
auto & menubar = toolbar_container . add < GUI : : Toolbar > ( ) ;
2021-03-21 09:01:33 -03:00
2022-05-29 08:20:29 -03:00
m_play_action = GUI : : Action : : create ( " Play " , { Key_Space } , m_play_icon , [ & ] ( auto & ) {
2021-09-30 01:55:42 -03:00
toggle_pause ( ) ;
2022-05-29 08:17:55 -03:00
} ) ;
m_play_action - > set_enabled ( false ) ;
menubar . add_action ( * m_play_action ) ;
2021-03-21 09:01:33 -03:00
2022-05-29 08:20:29 -03:00
m_stop_action = GUI : : Action : : create ( " Stop " , { Key_S } , m_stop_icon , [ & ] ( auto & ) {
2021-09-30 01:55:42 -03:00
stop ( ) ;
2022-05-29 08:17:55 -03:00
} ) ;
m_stop_action - > set_enabled ( false ) ;
menubar . add_action ( * m_stop_action ) ;
2021-03-21 09:01:33 -03:00
2022-05-29 08:21:05 -03:00
menubar . add_separator ( ) ;
2021-09-30 01:55:42 -03:00
m_timestamp_label = menubar . add < GUI : : Label > ( ) ;
m_timestamp_label - > set_fixed_width ( 110 ) ;
2021-03-21 09:01:33 -03:00
2022-05-29 08:17:55 -03:00
// Filler label
2021-03-21 09:01:33 -03:00
menubar . add < GUI : : Label > ( ) ;
2022-05-29 08:17:55 -03:00
m_back_action = GUI : : Action : : create ( " Back " , m_back_icon , [ & ] ( auto & ) {
2021-09-30 01:55:42 -03:00
play_file_path ( playlist ( ) . previous ( ) ) ;
2022-05-29 08:17:55 -03:00
} ) ;
m_back_action - > set_enabled ( false ) ;
menubar . add_action ( * m_back_action ) ;
2021-03-21 09:01:33 -03:00
2022-05-29 08:17:55 -03:00
m_next_action = GUI : : Action : : create ( " Next " , m_next_icon , [ & ] ( auto & ) {
2021-09-30 01:55:42 -03:00
play_file_path ( playlist ( ) . next ( ) ) ;
2022-05-29 08:17:55 -03:00
} ) ;
m_next_action - > set_enabled ( false ) ;
menubar . add_action ( * m_next_action ) ;
2021-03-21 09:01:33 -03:00
2022-05-29 08:21:05 -03:00
menubar . add_separator ( ) ;
2023-02-28 18:52:43 -03:00
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 ) ;
2021-03-21 09:01:33 -03:00
m_volume_label = & menubar . add < GUI : : Label > ( ) ;
m_volume_label - > set_fixed_width ( 30 ) ;
2021-10-30 23:49:46 -03:00
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 ) ;
2021-03-21 09:01:33 -03:00
2021-10-30 23:49:46 -03:00
m_volume_slider - > on_change = [ & ] ( int value ) {
2021-03-21 09:01:33 -03:00
double volume = m_nonlinear_volume_slider ? ( double ) ( value * value ) / ( 100 * 100 ) : value / 100. ;
set_volume ( volume ) ;
} ;
set_nonlinear_volume_slider ( false ) ;
2021-09-30 01:55:42 -03:00
done_initializing ( ) ;
2021-03-21 09:01:33 -03:00
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : set_nonlinear_volume_slider ( bool nonlinear )
2021-03-21 09:01:33 -03:00
{
m_nonlinear_volume_slider = nonlinear ;
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : drag_enter_event ( GUI : : DragEvent & event )
2022-11-04 18:32:17 -03:00
{
auto const & mime_types = event . mime_types ( ) ;
2023-09-21 14:02:52 -03:00
if ( mime_types . contains_slow ( " text/uri-list " sv ) )
2022-11-04 18:32:17 -03:00
event . accept ( ) ;
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : drop_event ( GUI : : DropEvent & event )
2021-03-21 09:01:33 -03:00
{
event . accept ( ) ;
if ( event . mime_data ( ) . has_urls ( ) ) {
auto urls = event . mime_data ( ) . urls ( ) ;
if ( urls . is_empty ( ) )
return ;
2021-07-12 12:58:54 -03:00
window ( ) - > move_to_front ( ) ;
2021-09-30 01:55:42 -03:00
// FIXME: Add all paths from drop event to the playlist
2023-04-14 16:12:03 -03:00
play_file_path ( urls . first ( ) . serialize_path ( ) ) ;
2021-03-21 09:01:33 -03:00
}
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : keydown_event ( GUI : : KeyEvent & event )
2021-09-02 09:23:28 -03:00
{
2021-10-30 23:56:21 -03:00
if ( event . key ( ) = = Key_Up )
2021-10-31 15:37:17 -03:00
m_volume_slider - > increase_slider_by_page_steps ( 1 ) ;
2021-10-30 23:56:21 -03:00
if ( event . key ( ) = = Key_Down )
2021-10-31 15:38:15 -03:00
m_volume_slider - > decrease_slider_by_page_steps ( 1 ) ;
2021-10-30 23:56:21 -03:00
2021-09-02 09:23:28 -03:00
GUI : : Widget : : keydown_event ( event ) ;
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : set_playlist_visible ( bool visible )
2021-03-21 09:01:33 -03:00
{
2021-09-30 01:55:42 -03:00
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 ) ;
}
2021-03-23 20:12:50 -03:00
}
2023-07-19 10:33:06 -03:00
RefPtr < Gfx : : Bitmap > SoundPlayerWidget : : get_image_from_music_file ( )
2023-03-19 13:36:51 -03:00
{
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 ;
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : play_state_changed ( Player : : PlayState state )
2021-03-23 20:12:50 -03:00
{
2022-05-29 08:17:55 -03:00
sync_previous_next_actions ( ) ;
2021-09-30 01:55:42 -03:00
2022-05-29 08:17:55 -03:00
m_play_action - > set_enabled ( state ! = PlayState : : NoFileLoaded ) ;
m_play_action - > set_icon ( state = = PlayState : : Playing ? m_pause_icon : m_play_icon ) ;
2022-11-19 11:56:36 -03:00
m_play_action - > set_text ( state = = PlayState : : Playing ? " Pause " sv : " Play " sv ) ;
2021-09-30 01:55:42 -03:00
2022-05-29 08:17:55 -03:00
m_stop_action - > set_enabled ( state ! = PlayState : : Stopped & & state ! = PlayState : : NoFileLoaded ) ;
2021-09-30 01:55:42 -03:00
m_playback_progress_slider - > set_enabled ( state ! = PlayState : : NoFileLoaded ) ;
2023-05-01 14:07:37 -03:00
if ( state = = PlayState : : Stopped ) {
m_playback_progress_slider - > set_value ( m_playback_progress_slider - > min ( ) , GUI : : AllowCallback : : No ) ;
m_visualization - > reset_buffer ( ) ;
}
2021-03-21 09:01:33 -03:00
}
2021-03-25 21:26:12 -03:00
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : loop_mode_changed ( Player : : LoopMode )
2021-03-25 21:26:12 -03:00
{
2021-09-30 01:55:42 -03:00
}
2021-03-25 21:26:12 -03:00
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : mute_changed ( bool muted )
2021-11-01 22:31:05 -03:00
{
2023-02-28 18:52:43 -03:00
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 ) ;
2021-11-01 22:31:05 -03:00
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : sync_previous_next_actions ( )
2021-09-30 11:41:00 -03:00
{
2022-05-29 08:17:55 -03:00
m_back_action - > set_enabled ( playlist ( ) . size ( ) > 1 & & ! playlist ( ) . shuffling ( ) ) ;
m_next_action - > set_enabled ( playlist ( ) . size ( ) > 1 ) ;
2021-09-30 11:41:00 -03:00
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : shuffle_mode_changed ( Player : : ShuffleMode )
2021-09-30 11:41:00 -03:00
{
2022-05-29 08:17:55 -03:00
sync_previous_next_actions ( ) ;
2021-09-30 11:41:00 -03:00
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : time_elapsed ( int seconds )
2021-09-30 01:55:42 -03:00
{
2023-07-19 07:05:37 -03:00
m_timestamp_label - > set_text ( String : : formatted ( " Elapsed: {} " , human_readable_digital_time ( seconds ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-09-30 01:55:42 -03:00
}
2021-03-25 21:26:12 -03:00
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : file_name_changed ( StringView name )
2021-09-30 01:55:42 -03:00
{
2022-02-27 22:18:28 -03:00
m_visualization - > start_new_file ( name ) ;
2023-03-07 13:32:05 -03:00
DeprecatedString title = name ;
if ( playback_manager ( ) . loader ( ) ) {
auto const & metadata = playback_manager ( ) . loader ( ) - > metadata ( ) ;
2023-08-07 23:26:17 -03:00
if ( auto artists_or_error = metadata . all_artists ( " / " _string ) ;
2023-03-07 13:32:05 -03:00
! 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 ) ) ;
2021-03-25 21:26:12 -03:00
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : total_samples_changed ( int total_samples )
2021-03-25 21:26:12 -03:00
{
2021-09-30 01:55:42 -03:00
m_playback_progress_slider - > set_max ( total_samples ) ;
m_playback_progress_slider - > set_page_step ( total_samples / 10 ) ;
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : sound_buffer_played ( FixedArray < Audio : : Sample > const & buffer , int sample_rate , int samples_played )
2021-09-30 01:55:42 -03:00
{
m_visualization - > set_buffer ( buffer ) ;
m_visualization - > set_samplerate ( sample_rate ) ;
2022-03-25 17:40:54 -03:00
// If the user is currently dragging the slider, don't interfere.
2023-02-05 22:20:03 -03:00
if ( ! m_playback_progress_slider - > knob_dragging ( ) )
m_playback_progress_slider - > set_value ( samples_played , GUI : : AllowCallback : : No ) ;
2021-09-30 01:55:42 -03:00
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : volume_changed ( double volume )
2021-09-30 01:55:42 -03:00
{
2023-04-29 11:41:48 -03:00
m_volume_label - > set_text ( String : : formatted ( " {}% " , static_cast < int > ( volume * 100 ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-03-25 21:26:12 -03:00
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : playlist_loaded ( StringView path , bool loaded )
2021-03-25 21:26:12 -03:00
{
2021-09-30 01:55:42 -03:00
if ( ! loaded ) {
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show ( & m_window , DeprecatedString : : formatted ( " Could not load playlist at \" {} \" . " , path ) , " Error opening playlist " sv , GUI : : MessageBox : : Type : : Error ) ;
2021-09-30 01:55:42 -03:00
return ;
2021-03-25 21:26:12 -03:00
}
2021-09-30 01:55:42 -03:00
set_playlist_visible ( true ) ;
play_file_path ( playlist ( ) . next ( ) ) ;
}
2023-07-19 10:33:06 -03:00
void SoundPlayerWidget : : audio_load_error ( StringView path , StringView error_string )
2021-09-30 01:55:42 -03:00
{
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show ( & m_window , DeprecatedString : : formatted ( " Failed to load audio file: {} ({}) " , path , error_string . is_null ( ) ? " Unknown error " sv : error_string ) ,
2022-07-11 14:32:29 -03:00
" Filetype error " sv , GUI : : MessageBox : : Type : : Error ) ;
2021-03-25 21:26:12 -03:00
}