Serenity Operating System
at master 164 lines 6.7 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2021, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "AlbumCoverVisualizationWidget.h" 9#include "BarsVisualizationWidget.h" 10#include "Player.h" 11#include "SampleWidget.h" 12#include "SoundPlayerWidgetAdvancedView.h" 13#include <LibAudio/ConnectionToServer.h> 14#include <LibAudio/FlacLoader.h> 15#include <LibCore/ArgsParser.h> 16#include <LibCore/System.h> 17#include <LibGUI/Action.h> 18#include <LibGUI/ActionGroup.h> 19#include <LibGUI/Application.h> 20#include <LibGUI/FilePicker.h> 21#include <LibGUI/Menu.h> 22#include <LibGUI/Menubar.h> 23#include <LibGUI/Window.h> 24#include <LibGfx/CharacterBitmap.h> 25#include <LibImageDecoderClient/Client.h> 26#include <LibMain/Main.h> 27 28ErrorOr<int> serenity_main(Main::Arguments arguments) 29{ 30 TRY(Core::System::pledge("stdio recvfd sendfd rpath thread unix proc")); 31 32 StringView file_path; 33 34 Core::ArgsParser args_parser; 35 args_parser.add_positional_argument(file_path, "Path to audio file to play", "file", Core::ArgsParser::Required::No); 36 args_parser.parse(arguments); 37 38 auto app = TRY(GUI::Application::try_create(arguments)); 39 auto audio_client = TRY(Audio::ConnectionToServer::try_create()); 40 auto decoder_client = TRY(ImageDecoderClient::Client::try_create()); 41 42 TRY(Core::System::pledge("stdio recvfd sendfd rpath thread proc")); 43 44 auto app_icon = GUI::Icon::default_icon("app-sound-player"sv); 45 46 auto window = TRY(GUI::Window::try_create()); 47 window->set_title("Sound Player"); 48 window->set_icon(app_icon.bitmap_for_size(16)); 49 50 // start in advanced view by default 51 Player* player = TRY(window->set_main_widget<SoundPlayerWidgetAdvancedView>(window, audio_client)); 52 53 if (!file_path.is_empty()) { 54 player->play_file_path(file_path); 55 if (player->is_playlist(file_path)) 56 player->set_loop_mode(Player::LoopMode::Playlist); 57 } 58 59 auto file_menu = TRY(window->try_add_menu("&File")); 60 TRY(file_menu->try_add_action(GUI::CommonActions::make_open_action([&](auto&) { 61 Optional<DeprecatedString> path = GUI::FilePicker::get_open_filepath(window, "Open sound file..."); 62 if (path.has_value()) { 63 player->play_file_path(path.value()); 64 } 65 }))); 66 67 TRY(file_menu->try_add_separator()); 68 TRY(file_menu->try_add_action(GUI::CommonActions::make_quit_action([&](auto&) { 69 app->quit(); 70 }))); 71 72 auto playback_menu = TRY(window->try_add_menu("&Playback")); 73 GUI::ActionGroup loop_actions; 74 loop_actions.set_exclusive(true); 75 auto loop_none = GUI::Action::create_checkable("&No Loop", { Mod_Ctrl, Key_N }, [&](auto&) { 76 player->set_loop_mode(Player::LoopMode::None); 77 }); 78 loop_actions.add_action(loop_none); 79 TRY(playback_menu->try_add_action(loop_none)); 80 81 auto loop_file = GUI::Action::create_checkable("Loop &File", { Mod_Ctrl, Key_F }, [&](auto&) { 82 player->set_loop_mode(Player::LoopMode::File); 83 }); 84 loop_actions.add_action(loop_file); 85 TRY(playback_menu->try_add_action(loop_file)); 86 87 auto loop_playlist = GUI::Action::create_checkable("Loop &Playlist", { Mod_Ctrl, Key_P }, [&](auto&) { 88 player->set_loop_mode(Player::LoopMode::Playlist); 89 }); 90 loop_actions.add_action(loop_playlist); 91 playback_menu->add_action(loop_playlist); 92 93 auto linear_volume_slider = GUI::Action::create_checkable("&Nonlinear Volume Slider", [&](auto& action) { 94 static_cast<SoundPlayerWidgetAdvancedView*>(player)->set_nonlinear_volume_slider(action.is_checked()); 95 }); 96 TRY(playback_menu->try_add_separator()); 97 TRY(playback_menu->try_add_action(linear_volume_slider)); 98 TRY(playback_menu->try_add_separator()); 99 100 auto playlist_toggle = GUI::Action::create_checkable("&Show Playlist", [&](auto& action) { 101 static_cast<SoundPlayerWidgetAdvancedView*>(player)->set_playlist_visible(action.is_checked()); 102 }); 103 if (player->loop_mode() == Player::LoopMode::Playlist) { 104 playlist_toggle->set_checked(true); 105 loop_playlist->set_checked(true); 106 } else { 107 loop_none->set_checked(true); 108 } 109 TRY(playback_menu->try_add_action(playlist_toggle)); 110 111 auto shuffle_mode = GUI::Action::create_checkable("S&huffle Playlist", [&](auto& action) { 112 if (action.is_checked()) 113 player->set_shuffle_mode(Player::ShuffleMode::Shuffling); 114 else 115 player->set_shuffle_mode(Player::ShuffleMode::None); 116 }); 117 TRY(playback_menu->try_add_action(shuffle_mode)); 118 119 auto visualization_menu = TRY(window->try_add_menu("&Visualization")); 120 GUI::ActionGroup visualization_actions; 121 visualization_actions.set_exclusive(true); 122 123 auto bars = GUI::Action::create_checkable("&Bars", [&](auto&) { 124 static_cast<SoundPlayerWidgetAdvancedView*>(player)->set_visualization<BarsVisualizationWidget>(); 125 }); 126 bars->set_checked(true); 127 TRY(visualization_menu->try_add_action(bars)); 128 visualization_actions.add_action(bars); 129 130 auto samples = GUI::Action::create_checkable("&Samples", [&](auto&) { 131 static_cast<SoundPlayerWidgetAdvancedView*>(player)->set_visualization<SampleWidget>(); 132 }); 133 TRY(visualization_menu->try_add_action(samples)); 134 visualization_actions.add_action(samples); 135 136 auto album_cover_visualization = GUI::Action::create_checkable("&Album Cover", [&](auto&) { 137 auto get_image_from_music_file = [&player, &decoder_client]() -> RefPtr<Gfx::Bitmap> { 138 auto const& pictures = player->pictures(); 139 140 if (pictures.is_empty()) 141 return {}; 142 143 // FIXME: We randomly select the first picture available for the track, 144 // We might want to hardcode or let the user set a preference. 145 auto decoded_image_or_error = decoder_client->decode_image(pictures[0].data); 146 if (!decoded_image_or_error.has_value()) 147 return {}; 148 149 auto const decoded_image = decoded_image_or_error.release_value(); 150 return decoded_image.frames[0].bitmap; 151 }; 152 153 static_cast<SoundPlayerWidgetAdvancedView*>(player)->set_visualization<AlbumCoverVisualizationWidget>(get_image_from_music_file); 154 }); 155 TRY(visualization_menu->try_add_action(album_cover_visualization)); 156 visualization_actions.add_action(album_cover_visualization); 157 158 auto help_menu = TRY(window->try_add_menu("&Help")); 159 TRY(help_menu->try_add_action(GUI::CommonActions::make_command_palette_action(window))); 160 TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("Sound Player", app_icon, window))); 161 162 window->show(); 163 return app->exec(); 164}