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
*/
# include "SoundPlayerWidgetAdvancedView.h"
# include "BarsVisualizationWidget.h"
2021-03-25 21:26:12 -03:00
# include "Common.h"
# include "M3UParser.h"
2021-03-21 09:01:33 -03:00
# include "PlaybackManager.h"
2021-03-25 21:26:12 -03:00
# include <AK/LexicalPath.h>
2021-03-21 09:01:33 -03:00
# include <AK/SIMD.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>
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>
2021-03-23 20:12:50 -03:00
SoundPlayerWidgetAdvancedView : : SoundPlayerWidgetAdvancedView ( GUI : : Window & window , PlayerState & state )
: Player ( state )
, m_window ( window )
2021-03-21 09:01:33 -03:00
{
window . resize ( 455 , 350 ) ;
2021-03-25 21:26:12 -03:00
window . set_minimum_size ( 600 , 130 ) ;
2021-03-21 09:01:33 -03:00
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-04-23 11:46:57 -03:00
m_playlist_model = adopt_ref ( * new PlaylistModel ( ) ) ;
2021-03-25 21:26:12 -03:00
2021-09-04 07:58:50 -03:00
m_playlist_widget = PlaylistWidget : : construct ( ) ;
m_playlist_widget - > set_data_model ( m_playlist_model ) ;
m_playlist_widget - > set_fixed_width ( 150 ) ;
2021-03-25 21:26:12 -03:00
m_player_view - > set_layout < GUI : : VerticalBoxLayout > ( ) ;
2021-03-21 09:01:33 -03:00
2021-07-21 13:02:15 -03:00
m_play_icon = Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/play.png " ) ;
m_pause_icon = Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/pause.png " ) ;
m_stop_icon = Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/stop.png " ) ;
m_back_icon = Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/go-back.png " ) ;
m_next_icon = Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/go-forward.png " ) ;
2021-03-21 09:01:33 -03:00
2021-03-25 21:26:12 -03:00
m_visualization = m_player_view - > add < BarsVisualizationWidget > ( ) ;
2021-03-21 09:01:33 -03:00
2021-06-01 22:45:09 -03:00
// Set a temporary value for total samples.
// This value will be set properly when we load a new file.
2021-06-19 17:17:04 -03:00
const int total_samples = this - > manager ( ) . total_length ( ) * this - > manager ( ) . device_sample_rate ( ) ;
2021-06-01 22:45:09 -03:00
2021-03-25 21:26:12 -03:00
m_playback_progress_slider = m_player_view - > add < AutoSlider > ( Orientation : : Horizontal ) ;
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 ) ;
2021-06-01 22:45:09 -03:00
m_playback_progress_slider - > set_max ( total_samples ) ;
m_playback_progress_slider - > set_page_step ( total_samples / 10 ) ;
2021-03-21 09:01:33 -03:00
m_playback_progress_slider - > on_knob_released = [ & ] ( int value ) {
2021-03-23 20:12:50 -03:00
this - > manager ( ) . seek ( 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
m_play_button = menubar . add < GUI : : Button > ( ) ;
2021-03-23 20:12:50 -03:00
m_play_button - > set_icon ( is_paused ( ) ? ( ! has_loaded_file ( ) ? * m_play_icon : * m_pause_icon ) : * m_pause_icon ) ;
2021-03-21 09:01:33 -03:00
m_play_button - > set_fixed_width ( 50 ) ;
2021-03-23 20:12:50 -03:00
m_play_button - > set_enabled ( has_loaded_file ( ) ) ;
2021-03-21 09:01:33 -03:00
m_play_button - > on_click = [ & ] ( unsigned ) {
2021-03-23 20:12:50 -03:00
bool paused = this - > manager ( ) . toggle_pause ( ) ;
set_paused ( paused ) ;
2021-03-21 09:01:33 -03:00
m_play_button - > set_icon ( paused ? * m_play_icon : * m_pause_icon ) ;
2021-06-03 19:18:54 -03:00
m_stop_button - > set_enabled ( has_loaded_file ( ) ) ;
2021-03-21 09:01:33 -03:00
} ;
m_stop_button = menubar . add < GUI : : Button > ( ) ;
m_stop_button - > set_icon ( * m_stop_icon ) ;
m_stop_button - > set_fixed_width ( 50 ) ;
2021-03-23 20:12:50 -03:00
m_stop_button - > set_enabled ( has_loaded_file ( ) ) ;
2021-03-21 09:01:33 -03:00
m_stop_button - > on_click = [ & ] ( unsigned ) {
2021-03-23 20:12:50 -03:00
this - > manager ( ) . stop ( ) ;
set_stopped ( true ) ;
2021-03-21 09:01:33 -03:00
m_play_button - > set_icon ( * m_play_icon ) ;
m_stop_button - > set_enabled ( false ) ;
} ;
auto & timestamp_label = menubar . add < GUI : : Label > ( ) ;
timestamp_label . set_fixed_width ( 110 ) ;
timestamp_label . set_text ( " Elapsed: 00:00:00 " ) ;
// filler_label
menubar . add < GUI : : Label > ( ) ;
2021-03-23 20:12:50 -03:00
m_back_button = menubar . add < GUI : : Button > ( ) ;
m_back_button - > set_fixed_width ( 50 ) ;
m_back_button - > set_icon ( * m_back_icon ) ;
m_back_button - > set_enabled ( has_loaded_file ( ) ) ;
2021-03-25 21:26:12 -03:00
m_back_button - > on_click = [ & ] ( unsigned ) {
if ( ! m_playlist_model . is_null ( ) ) {
auto it = m_playlist_model - > items ( ) . find_if ( [ & ] ( const M3UEntry & e ) { return e . path = = loaded_filename ( ) ; } ) ;
if ( it . index ( ) = = 0 ) {
open_file ( m_playlist_model - > items ( ) . at ( m_playlist_model - > items ( ) . size ( ) - 1 ) . path ) ;
} else
open_file ( ( it - 1 ) - > path ) ;
play ( ) ;
}
} ;
2021-03-21 09:01:33 -03:00
2021-03-23 20:12:50 -03:00
m_next_button = menubar . add < GUI : : Button > ( ) ;
m_next_button - > set_fixed_width ( 50 ) ;
m_next_button - > set_icon ( * m_next_icon ) ;
m_next_button - > set_enabled ( has_loaded_file ( ) ) ;
2021-03-25 21:26:12 -03:00
m_next_button - > on_click = [ & ] ( unsigned ) {
if ( ! m_playlist_model . is_null ( ) ) {
auto it = m_playlist_model - > items ( ) . find_if ( [ & ] ( const M3UEntry & e ) { return e . path = = loaded_filename ( ) ; } ) ;
if ( it . index ( ) + 1 = = m_playlist_model - > items ( ) . size ( ) ) {
open_file ( m_playlist_model - > items ( ) . at ( 0 ) . path ) ;
} else
open_file ( ( it + 1 ) - > path ) ;
play ( ) ;
}
} ;
2021-03-21 09:01:33 -03:00
m_volume_label = & menubar . add < GUI : : Label > ( ) ;
m_volume_label - > set_fixed_width ( 30 ) ;
m_volume_label - > set_text ( " 100% " ) ;
auto & volume_slider = menubar . add < GUI : : HorizontalSlider > ( ) ;
volume_slider . set_fixed_width ( 95 ) ;
volume_slider . set_min ( 0 ) ;
volume_slider . set_max ( 150 ) ;
volume_slider . set_value ( 100 ) ;
volume_slider . on_change = [ & ] ( int value ) {
double volume = m_nonlinear_volume_slider ? ( double ) ( value * value ) / ( 100 * 100 ) : value / 100. ;
m_volume_label - > set_text ( String : : formatted ( " {}% " , ( int ) ( volume * 100 ) ) ) ;
set_volume ( volume ) ;
} ;
set_volume ( 1. ) ;
set_nonlinear_volume_slider ( false ) ;
2021-03-23 20:12:50 -03:00
manager ( ) . on_update = [ & ] ( ) {
2021-06-19 17:17:04 -03:00
// Determine how many of the source file samples have played.
int samples_played = client_connection ( ) . get_played_samples ( ) ;
float source_to_dest_ratio = static_cast < float > ( loaded_file_samplerate ( ) ) / manager ( ) . device_sample_rate ( ) ;
samples_played * = source_to_dest_ratio ;
samples_played + = this - > manager ( ) . last_seek ( ) ;
int current_second = samples_played / loaded_file_samplerate ( ) ;
2021-03-21 09:01:33 -03:00
timestamp_label . set_text ( String : : formatted ( " Elapsed: {:02}:{:02}:{:02} " , current_second / 3600 , current_second / 60 , current_second % 60 ) ) ;
2021-06-01 22:45:09 -03:00
if ( ! m_playback_progress_slider - > mouse_is_down ( ) ) {
m_playback_progress_slider - > set_value ( samples_played ) ;
}
2021-03-21 09:01:33 -03:00
2021-03-23 20:12:50 -03:00
dynamic_cast < Visualization * > ( m_visualization . ptr ( ) ) - > set_buffer ( this - > manager ( ) . current_buffer ( ) ) ;
2021-06-19 17:17:04 -03:00
dynamic_cast < Visualization * > ( m_visualization . ptr ( ) ) - > set_samplerate ( manager ( ) . device_sample_rate ( ) ) ;
2021-03-25 21:26:12 -03:00
} ;
manager ( ) . on_load_sample_buffer = [ & ] ( Audio : : Buffer & ) {
//TODO: Implement an equalizer
return ;
2021-03-21 09:01:33 -03:00
} ;
2021-03-25 21:26:12 -03:00
manager ( ) . on_finished_playing = [ & ] {
m_play_button - > set_icon ( * m_play_icon ) ;
if ( looping ( ) ) {
open_file ( loaded_filename ( ) ) ;
2021-03-21 09:01:33 -03:00
return ;
2021-03-25 21:26:12 -03:00
}
if ( ! m_playlist_model . is_null ( ) & & ! m_playlist_model - > items ( ) . is_empty ( ) ) {
auto it = m_playlist_model - > items ( ) . find_if ( [ & ] ( const M3UEntry & e ) { return e . path = = loaded_filename ( ) ; } ) ;
if ( it . index ( ) + 1 = = m_playlist_model - > items ( ) . size ( ) ) {
if ( looping_playlist ( ) ) {
open_file ( m_playlist_model - > items ( ) . at ( 0 ) . path ) ;
return ;
}
} else
open_file ( ( it + 1 ) - > path ) ;
2021-03-21 09:01:33 -03:00
}
2021-06-19 17:17:04 -03:00
m_stop_button - > set_enabled ( false ) ;
2021-03-21 09:01:33 -03:00
} ;
}
void SoundPlayerWidgetAdvancedView : : open_file ( StringView path )
{
2021-03-25 21:26:12 -03:00
if ( ! Core : : File : : exists ( path ) ) {
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " File \" {} \" does not exist " , path ) , " Error opening file " , GUI : : MessageBox : : Type : : Error ) ;
return ;
}
if ( path . ends_with ( " .m3u " , AK : : CaseSensitivity : : CaseInsensitive ) | | path . ends_with ( " .m3u8 " , AK : : CaseSensitivity : : CaseInsensitive ) ) {
read_playlist ( path ) ;
return ;
}
2021-03-21 09:01:33 -03:00
NonnullRefPtr < Audio : : Loader > loader = Audio : : Loader : : create ( path ) ;
if ( loader - > has_error ( ) | | ! loader - > sample_rate ( ) ) {
const String error_string = loader - > error_string ( ) ;
GUI : : MessageBox : : show ( & m_window , String : : formatted ( " Failed to load audio file: {} ({}) " , path , error_string . is_null ( ) ? " Unknown error " : error_string ) ,
" Filetype error " , GUI : : MessageBox : : Type : : Error ) ;
return ;
}
2021-05-27 14:07:52 -03:00
m_window . set_title ( String : : formatted ( " {} - Sound Player " , loader - > file ( ) - > filename ( ) ) ) ;
2021-03-21 09:01:33 -03:00
m_playback_progress_slider - > set_max ( loader - > total_samples ( ) ) ;
2021-06-01 22:45:09 -03:00
m_playback_progress_slider - > set_page_step ( loader - > total_samples ( ) / 10 ) ;
2021-03-23 20:12:50 -03:00
m_playback_progress_slider - > set_enabled ( true ) ;
m_play_button - > set_enabled ( true ) ;
2021-03-25 21:28:56 -03:00
m_play_button - > set_icon ( * m_pause_icon ) ;
2021-03-23 20:12:50 -03:00
m_stop_button - > set_enabled ( true ) ;
manager ( ) . set_loader ( move ( loader ) ) ;
set_has_loaded_file ( true ) ;
2021-03-25 21:28:56 -03:00
set_loaded_file_samplerate ( loader - > sample_rate ( ) ) ;
2021-03-23 20:12:50 -03:00
set_loaded_filename ( path ) ;
2021-03-25 21:28:56 -03:00
play ( ) ;
2021-03-21 09:01:33 -03:00
}
void SoundPlayerWidgetAdvancedView : : set_nonlinear_volume_slider ( bool nonlinear )
{
m_nonlinear_volume_slider = nonlinear ;
}
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 ;
2021-07-12 12:58:54 -03:00
window ( ) - > move_to_front ( ) ;
2021-03-21 09:01:33 -03:00
open_file ( urls . first ( ) . path ( ) ) ;
}
}
2021-09-02 09:23:28 -03:00
void SoundPlayerWidgetAdvancedView : : keydown_event ( GUI : : KeyEvent & event )
{
if ( event . key ( ) = = Key_Space )
m_play_button - > click ( ) ;
GUI : : Widget : : keydown_event ( event ) ;
}
2021-03-21 09:01:33 -03:00
SoundPlayerWidgetAdvancedView : : ~ SoundPlayerWidgetAdvancedView ( )
{
2021-03-23 20:12:50 -03:00
manager ( ) . on_load_sample_buffer = nullptr ;
manager ( ) . on_update = nullptr ;
}
void SoundPlayerWidgetAdvancedView : : play ( )
{
manager ( ) . play ( ) ;
set_paused ( false ) ;
set_stopped ( false ) ;
2021-03-21 09:01:33 -03:00
}
2021-03-25 21:26:12 -03:00
void SoundPlayerWidgetAdvancedView : : read_playlist ( StringView path )
{
auto parser = M3UParser : : from_file ( path ) ;
auto items = parser - > parse ( true ) ;
VERIFY ( items - > size ( ) > 0 ) ;
try_fill_missing_info ( * items , path ) ;
for ( auto & item : * items )
m_playlist_model - > items ( ) . append ( item ) ;
set_playlist_visible ( true ) ;
2021-05-25 11:13:19 -03:00
m_playlist_model - > invalidate ( ) ;
2021-03-25 21:26:12 -03:00
open_file ( items - > at ( 0 ) . path ) ;
if ( items - > size ( ) > 1 ) {
m_back_button - > set_enabled ( true ) ;
m_next_button - > set_enabled ( true ) ;
} else {
m_back_button - > set_enabled ( false ) ;
m_next_button - > set_enabled ( false ) ;
}
}
void SoundPlayerWidgetAdvancedView : : set_playlist_visible ( bool visible )
{
if ( visible ) {
2021-09-04 07:58:50 -03:00
if ( ! m_playlist_widget - > parent ( ) ) {
m_player_view - > parent_widget ( ) - > add_child ( * m_playlist_widget ) ;
}
2021-03-25 21:26:12 -03:00
} else {
m_playlist_widget - > remove_from_parent ( ) ;
m_player_view - > set_max_width ( window ( ) - > width ( ) ) ;
}
}
void SoundPlayerWidgetAdvancedView : : try_fill_missing_info ( Vector < M3UEntry > & entries , StringView playlist_p )
{
LexicalPath playlist_path ( playlist_p ) ;
Vector < M3UEntry * > to_delete ;
for ( auto & entry : entries ) {
2021-07-20 07:21:59 -03:00
if ( ! LexicalPath ( entry . path ) . is_absolute ( ) ) {
entry . path = String : : formatted ( " {}/{} " , playlist_path . dirname ( ) , entry . path ) ;
2021-03-25 21:26:12 -03:00
}
if ( ! Core : : File : : exists ( entry . path ) ) {
2021-07-20 07:21:59 -03:00
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " The file \" {} \" present in the playlist does not exist or was not found. This file will be ignored. " , entry . path ) , " Error reading playlist " , GUI : : MessageBox : : Type : : Warning ) ;
2021-03-25 21:26:12 -03:00
to_delete . append ( & entry ) ;
continue ;
}
if ( ! entry . extended_info - > track_display_title . has_value ( ) )
2021-06-29 11:46:16 -03:00
entry . extended_info - > track_display_title = LexicalPath : : title ( entry . path ) ;
2021-03-25 21:26:12 -03:00
if ( ! entry . extended_info - > track_length_in_seconds . has_value ( ) ) {
2021-06-28 12:13:31 -03:00
if ( auto reader = Audio : : Loader : : create ( entry . path ) ; ! reader - > has_error ( ) )
entry . extended_info - > track_length_in_seconds = reader - > total_samples ( ) / reader - > sample_rate ( ) ;
2021-03-25 21:26:12 -03:00
//TODO: Implement embedded metadata extractor for other audio formats
}
//TODO: Implement a metadata parser for the uncomfortably numerous popular embedded metadata formats
if ( ! entry . extended_info - > file_size_in_bytes . has_value ( ) ) {
FILE * f = fopen ( entry . path . characters ( ) , " r " ) ;
VERIFY ( f ! = nullptr ) ;
fseek ( f , 0 , SEEK_END ) ;
entry . extended_info - > file_size_in_bytes = ftell ( f ) ;
fclose ( f ) ;
}
}
for ( M3UEntry * entry : to_delete )
entries . remove_first_matching ( [ & ] ( M3UEntry & e ) { return & e = = entry ; } ) ;
}