Serenity Operating System
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}