mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 20:57:35 +00:00
SoundPlayer: Load cover image from music files
When the visualization is set to "Album Cover", the player will now try to load the embedded image. On failure, it defaults to a "Cover" image file in the directory. In Player::play_file_path, file_name_changed now needs to be executed after that the loader have been set, to get the correct image.
This commit is contained in:
parent
27539440df
commit
7649feb26f
8 changed files with 58 additions and 10 deletions
|
@ -12,13 +12,19 @@
|
||||||
#include <LibGUI/Painter.h>
|
#include <LibGUI/Painter.h>
|
||||||
#include <LibGfx/Rect.h>
|
#include <LibGfx/Rect.h>
|
||||||
|
|
||||||
|
AlbumCoverVisualizationWidget::AlbumCoverVisualizationWidget(Function<RefPtr<Gfx::Bitmap>()> get_file_cover_from_player)
|
||||||
|
: m_get_file_cover_from_player(move(get_file_cover_from_player))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void AlbumCoverVisualizationWidget::paint_event(GUI::PaintEvent& event)
|
void AlbumCoverVisualizationWidget::paint_event(GUI::PaintEvent& event)
|
||||||
{
|
{
|
||||||
Frame::paint_event(event);
|
Frame::paint_event(event);
|
||||||
GUI::Painter painter(*this);
|
GUI::Painter painter(*this);
|
||||||
|
|
||||||
if (m_album_cover) {
|
auto const& cover = m_file_cover ? m_file_cover : m_album_cover;
|
||||||
auto album_cover_rect = m_album_cover->rect();
|
if (cover) {
|
||||||
|
auto album_cover_rect = cover->rect();
|
||||||
|
|
||||||
auto height_ratio = frame_inner_rect().height() / (float)album_cover_rect.height();
|
auto height_ratio = frame_inner_rect().height() / (float)album_cover_rect.height();
|
||||||
auto width_ratio = frame_inner_rect().width() / (float)album_cover_rect.width();
|
auto width_ratio = frame_inner_rect().width() / (float)album_cover_rect.width();
|
||||||
|
@ -27,7 +33,7 @@ void AlbumCoverVisualizationWidget::paint_event(GUI::PaintEvent& event)
|
||||||
Gfx::IntRect fitted_rect = { 0, 0, (int)(album_cover_rect.width() * scale), (int)(album_cover_rect.height() * scale) };
|
Gfx::IntRect fitted_rect = { 0, 0, (int)(album_cover_rect.width() * scale), (int)(album_cover_rect.height() * scale) };
|
||||||
fitted_rect.center_within(frame_inner_rect());
|
fitted_rect.center_within(frame_inner_rect());
|
||||||
|
|
||||||
painter.draw_scaled_bitmap(fitted_rect, *m_album_cover, m_album_cover->rect(), 1.0f);
|
painter.draw_scaled_bitmap(fitted_rect, *cover, cover->rect(), 1.0f);
|
||||||
} else {
|
} else {
|
||||||
if (!m_serenity_bg)
|
if (!m_serenity_bg)
|
||||||
m_serenity_bg = Gfx::Bitmap::try_load_from_file("/res/wallpapers/sunset-retro.png"sv).release_value_but_fixme_should_propagate_errors();
|
m_serenity_bg = Gfx::Bitmap::try_load_from_file("/res/wallpapers/sunset-retro.png"sv).release_value_but_fixme_should_propagate_errors();
|
||||||
|
@ -51,6 +57,12 @@ ErrorOr<NonnullRefPtr<Gfx::Bitmap>> AlbumCoverVisualizationWidget::get_album_cov
|
||||||
|
|
||||||
void AlbumCoverVisualizationWidget::start_new_file(StringView filename)
|
void AlbumCoverVisualizationWidget::start_new_file(StringView filename)
|
||||||
{
|
{
|
||||||
|
if (m_get_file_cover_from_player)
|
||||||
|
m_file_cover = m_get_file_cover_from_player();
|
||||||
|
|
||||||
|
if (m_file_cover)
|
||||||
|
return;
|
||||||
|
|
||||||
auto album_cover_or_error = get_album_cover(filename);
|
auto album_cover_or_error = get_album_cover(filename);
|
||||||
if (album_cover_or_error.is_error())
|
if (album_cover_or_error.is_error())
|
||||||
m_album_cover = nullptr;
|
m_album_cover = nullptr;
|
||||||
|
|
|
@ -14,6 +14,7 @@ class AlbumCoverVisualizationWidget final : public VisualizationWidget {
|
||||||
C_OBJECT(AlbumCoverVisualizationWidget)
|
C_OBJECT(AlbumCoverVisualizationWidget)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
AlbumCoverVisualizationWidget(Function<RefPtr<Gfx::Bitmap>()> get_file_cover_from_player);
|
||||||
~AlbumCoverVisualizationWidget() override = default;
|
~AlbumCoverVisualizationWidget() override = default;
|
||||||
void start_new_file(StringView) override;
|
void start_new_file(StringView) override;
|
||||||
|
|
||||||
|
@ -23,6 +24,9 @@ private:
|
||||||
AlbumCoverVisualizationWidget() = default;
|
AlbumCoverVisualizationWidget() = default;
|
||||||
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> get_album_cover(StringView const filename);
|
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> get_album_cover(StringView const filename);
|
||||||
|
|
||||||
|
Function<RefPtr<Gfx::Bitmap>()> m_get_file_cover_from_player;
|
||||||
|
|
||||||
RefPtr<Gfx::Bitmap> m_serenity_bg;
|
RefPtr<Gfx::Bitmap> m_serenity_bg;
|
||||||
RefPtr<Gfx::Bitmap> m_album_cover;
|
RefPtr<Gfx::Bitmap> m_album_cover;
|
||||||
|
RefPtr<Gfx::Bitmap> m_file_cover;
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,4 +19,4 @@ set(SOURCES
|
||||||
)
|
)
|
||||||
|
|
||||||
serenity_app(SoundPlayer ICON app-sound-player)
|
serenity_app(SoundPlayer ICON app-sound-player)
|
||||||
target_link_libraries(SoundPlayer PRIVATE LibAudio LibCore LibDSP LibGfx LibGUI LibIPC LibMain LibThreading)
|
target_link_libraries(SoundPlayer PRIVATE LibAudio LibCore LibDSP LibGfx LibGUI LibIPC LibMain LibThreading LibImageDecoderClient)
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
|
#include <LibAudio/FlacLoader.h>
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
|
|
||||||
Player::Player(Audio::ConnectionToServer& audio_client_connection)
|
Player::Player(Audio::ConnectionToServer& audio_client_connection)
|
||||||
|
@ -63,9 +64,9 @@ void Player::play_file_path(String const& path)
|
||||||
|
|
||||||
m_loaded_filename = path;
|
m_loaded_filename = path;
|
||||||
|
|
||||||
file_name_changed(path);
|
|
||||||
total_samples_changed(loader->total_samples());
|
total_samples_changed(loader->total_samples());
|
||||||
m_playback_manager.set_loader(move(loader));
|
m_playback_manager.set_loader(move(loader));
|
||||||
|
file_name_changed(path);
|
||||||
|
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
|
@ -156,3 +157,8 @@ void Player::seek(int sample)
|
||||||
{
|
{
|
||||||
m_playback_manager.seek(sample);
|
m_playback_manager.seek(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector<Audio::PictureData> const& Player::pictures() const
|
||||||
|
{
|
||||||
|
return m_playback_manager.loader()->pictures();
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "Playlist.h"
|
#include "Playlist.h"
|
||||||
#include "PlaylistWidget.h"
|
#include "PlaylistWidget.h"
|
||||||
#include <AK/RefPtr.h>
|
#include <AK/RefPtr.h>
|
||||||
|
#include <LibAudio/GenericTypes.h>
|
||||||
#include <LibAudio/Sample.h>
|
#include <LibAudio/Sample.h>
|
||||||
|
|
||||||
class Player {
|
class Player {
|
||||||
|
@ -75,6 +76,8 @@ public:
|
||||||
virtual void total_samples_changed(int) = 0;
|
virtual void total_samples_changed(int) = 0;
|
||||||
virtual void sound_buffer_played(FixedArray<Audio::Sample> const&, [[maybe_unused]] int sample_rate, [[maybe_unused]] int samples_played) = 0;
|
virtual void sound_buffer_played(FixedArray<Audio::Sample> const&, [[maybe_unused]] int sample_rate, [[maybe_unused]] int samples_played) = 0;
|
||||||
|
|
||||||
|
Vector<Audio::PictureData> const& pictures() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void done_initializing()
|
void done_initializing()
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,12 +25,12 @@ public:
|
||||||
void set_nonlinear_volume_slider(bool nonlinear);
|
void set_nonlinear_volume_slider(bool nonlinear);
|
||||||
void set_playlist_visible(bool visible);
|
void set_playlist_visible(bool visible);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T, typename... Args>
|
||||||
void set_visualization()
|
void set_visualization(Args... args)
|
||||||
{
|
{
|
||||||
m_visualization->remove_from_parent();
|
m_visualization->remove_from_parent();
|
||||||
update();
|
update();
|
||||||
auto new_visualization = T::construct();
|
auto new_visualization = T::construct(move(args)...);
|
||||||
m_player_view->insert_child_before(new_visualization, *static_cast<Core::Object*>(m_playback_progress_slider.ptr()));
|
m_player_view->insert_child_before(new_visualization, *static_cast<Core::Object*>(m_playback_progress_slider.ptr()));
|
||||||
m_visualization = new_visualization;
|
m_visualization = new_visualization;
|
||||||
if (!loaded_filename().is_empty())
|
if (!loaded_filename().is_empty())
|
||||||
|
|
|
@ -5,12 +5,16 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// FIXME: LibIPC Decoder and Encoder are sensitive to include order here
|
||||||
|
#include <LibImageDecoderClient/Client.h>
|
||||||
|
|
||||||
#include "AlbumCoverVisualizationWidget.h"
|
#include "AlbumCoverVisualizationWidget.h"
|
||||||
#include "BarsVisualizationWidget.h"
|
#include "BarsVisualizationWidget.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "SampleWidget.h"
|
#include "SampleWidget.h"
|
||||||
#include "SoundPlayerWidgetAdvancedView.h"
|
#include "SoundPlayerWidgetAdvancedView.h"
|
||||||
#include <LibAudio/ConnectionToServer.h>
|
#include <LibAudio/ConnectionToServer.h>
|
||||||
|
#include <LibAudio/FlacLoader.h>
|
||||||
#include <LibCore/System.h>
|
#include <LibCore/System.h>
|
||||||
#include <LibGUI/Action.h>
|
#include <LibGUI/Action.h>
|
||||||
#include <LibGUI/ActionGroup.h>
|
#include <LibGUI/ActionGroup.h>
|
||||||
|
@ -21,7 +25,6 @@
|
||||||
#include <LibGUI/Window.h>
|
#include <LibGUI/Window.h>
|
||||||
#include <LibGfx/CharacterBitmap.h>
|
#include <LibGfx/CharacterBitmap.h>
|
||||||
#include <LibMain/Main.h>
|
#include <LibMain/Main.h>
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
{
|
{
|
||||||
|
@ -29,6 +32,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
|
|
||||||
auto app = TRY(GUI::Application::try_create(arguments));
|
auto app = TRY(GUI::Application::try_create(arguments));
|
||||||
auto audio_client = TRY(Audio::ConnectionToServer::try_create());
|
auto audio_client = TRY(Audio::ConnectionToServer::try_create());
|
||||||
|
auto decoder_client = TRY(ImageDecoderClient::Client::try_create());
|
||||||
|
|
||||||
TRY(Core::System::pledge("stdio recvfd sendfd rpath thread"));
|
TRY(Core::System::pledge("stdio recvfd sendfd rpath thread"));
|
||||||
|
|
||||||
|
@ -125,7 +129,23 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
visualization_actions.add_action(samples);
|
visualization_actions.add_action(samples);
|
||||||
|
|
||||||
auto album_cover_visualization = GUI::Action::create_checkable("&Album Cover", [&](auto&) {
|
auto album_cover_visualization = GUI::Action::create_checkable("&Album Cover", [&](auto&) {
|
||||||
static_cast<SoundPlayerWidgetAdvancedView*>(player)->set_visualization<AlbumCoverVisualizationWidget>();
|
auto get_image_from_music_file = [&player, &decoder_client]() -> RefPtr<Gfx::Bitmap> {
|
||||||
|
auto const& pictures = player->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 = 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_cast<SoundPlayerWidgetAdvancedView*>(player)->set_visualization<AlbumCoverVisualizationWidget>(get_image_from_music_file);
|
||||||
});
|
});
|
||||||
TRY(visualization_menu->try_add_action(album_cover_visualization));
|
TRY(visualization_menu->try_add_action(album_cover_visualization));
|
||||||
visualization_actions.add_action(album_cover_visualization);
|
visualization_actions.add_action(album_cover_visualization);
|
||||||
|
|
|
@ -58,6 +58,8 @@ public:
|
||||||
virtual String format_name() = 0;
|
virtual String format_name() = 0;
|
||||||
virtual PcmSampleFormat pcm_format() = 0;
|
virtual PcmSampleFormat pcm_format() = 0;
|
||||||
|
|
||||||
|
Vector<PictureData> const& pictures() const { return m_pictures; };
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
StringView m_path;
|
StringView m_path;
|
||||||
OwnPtr<Core::Stream::SeekableStream> m_stream;
|
OwnPtr<Core::Stream::SeekableStream> m_stream;
|
||||||
|
@ -83,6 +85,7 @@ public:
|
||||||
u16 num_channels() const { return m_plugin->num_channels(); }
|
u16 num_channels() const { return m_plugin->num_channels(); }
|
||||||
String format_name() const { return m_plugin->format_name(); }
|
String format_name() const { return m_plugin->format_name(); }
|
||||||
u16 bits_per_sample() const { return pcm_bits_per_sample(m_plugin->pcm_format()); }
|
u16 bits_per_sample() const { return pcm_bits_per_sample(m_plugin->pcm_format()); }
|
||||||
|
Vector<PictureData> const& pictures() const { return m_plugin->pictures(); };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static Result<NonnullOwnPtr<LoaderPlugin>, LoaderError> try_create(StringView path);
|
static Result<NonnullOwnPtr<LoaderPlugin>, LoaderError> try_create(StringView path);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue