diff --git a/rpcs3/Emu/GameInfo.h b/rpcs3/Emu/GameInfo.h index d9a2d67449..d5ed2f6c08 100644 --- a/rpcs3/Emu/GameInfo.h +++ b/rpcs3/Emu/GameInfo.h @@ -22,4 +22,13 @@ struct GameInfo u32 resolution = 0; u64 size_on_disk = umax; + + std::string get_pam_path() const + { + if (icon_path.empty()) + { + return {}; + } + return icon_path.substr(0, icon_path.find_last_of("/")) + "/ICON1.PAM"; + } }; diff --git a/rpcs3/rpcs3qt/game_list_base.h b/rpcs3/rpcs3qt/game_list_base.h index 2d39402281..e10d70675c 100644 --- a/rpcs3/rpcs3qt/game_list_base.h +++ b/rpcs3/rpcs3qt/game_list_base.h @@ -19,6 +19,7 @@ struct gui_game_info bool hasCustomConfig = false; bool hasCustomPadConfig = false; bool has_hover_gif = false; + bool has_hover_pam = false; movie_item_base* item = nullptr; }; diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index f295a0107d..a182961352 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -614,6 +614,7 @@ void game_list_frame::OnParsingFinished() info.hasCustomConfig = fs::is_file(rpcs3::utils::get_custom_config_path(info.info.serial)); info.hasCustomPadConfig = fs::is_file(rpcs3::utils::get_custom_input_config_path(info.info.serial)); info.has_hover_gif = fs::is_file(game_icon_path + info.info.serial + "/hover.gif"); + info.has_hover_pam = fs::is_file(info.info.get_pam_path()); m_games.push(std::make_shared(std::move(info))); }; diff --git a/rpcs3/rpcs3qt/game_list_grid.cpp b/rpcs3/rpcs3qt/game_list_grid.cpp index 23e39fc1ec..2758d00e5b 100644 --- a/rpcs3/rpcs3qt/game_list_grid.cpp +++ b/rpcs3/rpcs3qt/game_list_grid.cpp @@ -75,16 +75,16 @@ void game_list_grid::populate( item->setToolTip(tr("%0 [%1]\n\nNotes:\n%2").arg(title).arg(serial).arg(notes)); } - item->set_icon_func([this, item, game](int) + item->set_icon_func([this, item, game](const QVideoFrame& frame) { if (!item || !game) { return; } - if (std::shared_ptr movie = item->movie(); movie && item->get_active()) + if (const QPixmap pixmap = item->get_movie_image(frame); item->get_active() && !pixmap.isNull()) { - item->set_icon(gui::utils::get_centered_pixmap(movie->currentPixmap(), m_icon_size, 0, 0, 1.0, Qt::FastTransformation)); + item->set_icon(gui::utils::get_centered_pixmap(pixmap, m_icon_size, 0, 0, 1.0, Qt::FastTransformation)); } else { @@ -92,21 +92,22 @@ void game_list_grid::populate( item->set_icon(game->pxmap); - if (!game->has_hover_gif) + if (!game->has_hover_gif && !game->has_hover_pam) { game->pxmap = {}; } - if (movie) - { - movie->stop(); - } + item->stop_movie(); } }); if (play_hover_movies && game->has_hover_gif) { - item->init_movie(game_icon_path % serial % "/hover.gif"); + item->set_movie_path(game_icon_path % serial % "/hover.gif"); + } + else if (play_hover_movies && game->has_hover_pam) + { + item->set_movie_path(QString::fromStdString(game->info.get_pam_path())); } if (selected_item_id == game->info.path + game->info.icon_path) diff --git a/rpcs3/rpcs3qt/game_list_table.cpp b/rpcs3/rpcs3qt/game_list_table.cpp index 99415d6dca..f1598e95cf 100644 --- a/rpcs3/rpcs3qt/game_list_table.cpp +++ b/rpcs3/rpcs3qt/game_list_table.cpp @@ -235,16 +235,16 @@ void game_list_table::populate( custom_table_widget_item* icon_item = new custom_table_widget_item; game->item = icon_item; - icon_item->set_icon_func([this, icon_item, game](int) + icon_item->set_icon_func([this, icon_item, game](const QVideoFrame& frame) { if (!icon_item || !game) { return; } - if (std::shared_ptr movie = icon_item->movie(); movie && icon_item->get_active()) + if (const QPixmap pixmap = icon_item->get_movie_image(frame); icon_item->get_active() && !pixmap.isNull()) { - icon_item->setData(Qt::DecorationRole, movie->currentPixmap().scaled(m_icon_size, Qt::KeepAspectRatio)); + icon_item->setData(Qt::DecorationRole, pixmap.scaled(m_icon_size, Qt::KeepAspectRatio)); } else { @@ -252,15 +252,12 @@ void game_list_table::populate( icon_item->setData(Qt::DecorationRole, game->pxmap); - if (!game->has_hover_gif) + if (!game->has_hover_gif && !game->has_hover_pam) { game->pxmap = {}; } - if (movie) - { - movie->stop(); - } + icon_item->stop_movie(); } }); @@ -288,7 +285,11 @@ void game_list_table::populate( if (play_hover_movies && game->has_hover_gif) { - icon_item->init_movie(game_icon_path % serial % "/hover.gif"); + icon_item->set_movie_path(game_icon_path % serial % "/hover.gif"); + } + else if (play_hover_movies && game->has_hover_pam) + { + icon_item->set_movie_path(QString::fromStdString(game->info.get_pam_path())); } icon_item->setData(Qt::UserRole, index, true); diff --git a/rpcs3/rpcs3qt/movie_item_base.cpp b/rpcs3/rpcs3qt/movie_item_base.cpp index 48cd3f5af3..d413f4d0b3 100644 --- a/rpcs3/rpcs3qt/movie_item_base.cpp +++ b/rpcs3/rpcs3qt/movie_item_base.cpp @@ -1,6 +1,8 @@ #include "stdafx.h" #include "movie_item_base.h" +#include + movie_item_base::movie_item_base() { init_pointers(); @@ -13,6 +15,11 @@ movie_item_base::~movie_item_base() m_movie->stop(); } + if (m_media_player) + { + m_media_player->stop(); + } + wait_for_icon_loading(true); wait_for_size_on_disk_loading(true); } @@ -25,33 +32,136 @@ void movie_item_base::init_pointers() void movie_item_base::set_active(bool active) { - if (!std::exchange(m_active, active) && active && m_movie) + if (!std::exchange(m_active, active) && active) { - m_movie->jumpToFrame(1); - m_movie->start(); + init_movie(); + + if (m_movie) + { + m_movie->jumpToFrame(1); + m_movie->start(); + } + + if (m_media_player) + { + m_media_player->play(); + } } } -void movie_item_base::init_movie(const QString& path) +void movie_item_base::init_movie() { - if (path.isEmpty() || !m_icon_callback) return; - - m_movie.reset(new QMovie(path)); - - if (!m_movie->isValid()) + if (m_movie || m_media_player) { - m_movie.reset(); + // Already initialized return; } - QObject::connect(m_movie.get(), &QMovie::frameChanged, m_movie.get(), m_icon_callback); + if (!m_icon_callback || m_movie_path.isEmpty() || !QFile::exists(m_movie_path)) + { + m_movie_path.clear(); + return; + } + + const QString lower = m_movie_path.toLower(); + + if (lower.endsWith(".gif")) + { + m_movie.reset(new QMovie(m_movie_path)); + m_movie_path.clear(); + + if (!m_movie->isValid()) + { + m_movie.reset(); + return; + } + + QObject::connect(m_movie.get(), &QMovie::frameChanged, m_movie.get(), [this](int) + { + m_icon_callback({}); + }); + return; + } + + if (lower.endsWith(".pam")) + { + // We can't set PAM files as source of the video player, so we have to feed them as raw data. + QFile file(m_movie_path); + if (!file.open(QFile::OpenModeFlag::ReadOnly)) + { + return; + } + + // TODO: Decode the pam properly before pushing it to the player + m_movie_data = file.readAll(); + if (m_movie_data.isEmpty()) + { + return; + } + + m_movie_buffer.reset(new QBuffer(&m_movie_data)); + m_movie_buffer->open(QIODevice::ReadOnly); + } + + m_video_sink.reset(new QVideoSink()); + QObject::connect(m_video_sink.get(), &QVideoSink::videoFrameChanged, m_video_sink.get(), [this](const QVideoFrame& frame) + { + m_icon_callback(frame); + }); + + m_media_player.reset(new QMediaPlayer()); + m_media_player->setVideoSink(m_video_sink.get()); + m_media_player->setLoops(QMediaPlayer::Infinite); + + if (m_movie_buffer) + { + m_media_player->setSourceDevice(m_movie_buffer.get()); + } + else + { + m_media_player->setSource(m_movie_path); + } +} + +void movie_item_base::stop_movie() +{ + if (m_movie) + { + m_movie->stop(); + } + + m_video_sink.reset(); + m_media_player.reset(); + m_movie_buffer.reset(); + m_movie_data.clear(); +} + +QPixmap movie_item_base::get_movie_image(const QVideoFrame& frame) const +{ + if (!m_active) + { + return {}; + } + + if (m_movie) + { + return m_movie->currentPixmap(); + } + + if (!frame.isValid()) + { + return {}; + } + + // Get image. This usually also converts the image to ARGB32. + return QPixmap::fromImage(frame.toImage()); } void movie_item_base::call_icon_func() const { if (m_icon_callback) { - m_icon_callback(0); + m_icon_callback({}); } } diff --git a/rpcs3/rpcs3qt/movie_item_base.h b/rpcs3/rpcs3qt/movie_item_base.h index ae6071eadc..f13bce91b2 100644 --- a/rpcs3/rpcs3qt/movie_item_base.h +++ b/rpcs3/rpcs3qt/movie_item_base.h @@ -6,11 +6,16 @@ #include #include +#include +#include +#include +#include +#include #include #include -using icon_callback_t = std::function; +using icon_callback_t = std::function; using icon_load_callback_t = std::function; using size_calc_callback_t = std::function; @@ -29,12 +34,14 @@ public: return m_active; } - [[nodiscard]] std::shared_ptr movie() const + void set_movie_path(QString path) { - return m_movie; + m_movie_path = std::move(path); } - void init_movie(const QString& path); + void init_movie(); + void stop_movie(); + QPixmap get_movie_image(const QVideoFrame& frame) const; void call_icon_func() const; void set_icon_func(const icon_callback_t& func); @@ -71,6 +78,11 @@ public: shared_mutex pixmap_mutex; protected: + QString m_movie_path; + QByteArray m_movie_data{}; + std::unique_ptr m_movie_buffer; + std::unique_ptr m_media_player; + std::shared_ptr m_video_sink; std::shared_ptr m_movie; private: