diff --git a/rpcs3/Emu/Cell/Modules/cellMusic.cpp b/rpcs3/Emu/Cell/Modules/cellMusic.cpp index c23bf274b0..d3b40f729b 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusic.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusic.cpp @@ -79,33 +79,38 @@ struct music_state music_state() { handler = Emu.GetCallbacks().get_music_handler(); - handler->set_status_callback([this](music_handler_base::player_status status) + handler->set_event_status_callback([this](u32 status) { - // TODO: disabled until I find a game that uses CELL_MUSIC_EVENT_STATUS_NOTIFICATION - return; - if (!func) { return; } - s32 result = CELL_OK; - + // Known to be used by NFS: Hot Pursuit + sysutil_register_cb([this, state = status](ppu_thread& ppu) -> s32 + { + cellMusic.notice("Sending status notification %d", state); + func(ppu, CELL_MUSIC_EVENT_STATUS_NOTIFICATION, vm::addr_t(state), userData); + return CELL_OK; + }); + }); + handler->set_playback_status_callback([this](music_handler_base::player_status status) + { switch (status) { case music_handler_base::player_status::end_of_media: - result = CELL_MUSIC_PLAYBACK_FINISHED; + // Let's just play the next song for now + if (current_selection_context.content_type == CELL_SEARCH_CONTENTTYPE_MUSICLIST && handler->get_state() == CELL_MUSIC_PB_STATUS_PLAY) + { + if (const error_code error = set_playback_command(CELL_MUSIC_PB_CMD_NEXT)) + { + cellMusic.error("Failed to play next track. error=0x%x", +error); + } + } break; default: return; } - - sysutil_register_cb([this, &result](ppu_thread& ppu) -> s32 - { - cellMusic.notice("Sending status notification %d", result); - func(ppu, CELL_MUSIC_EVENT_STATUS_NOTIFICATION, vm::addr_t(result), userData); - return CELL_OK; - }); }); } diff --git a/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp b/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp index 42f4a0fb6f..d6237c2c63 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp @@ -287,6 +287,8 @@ u32 music_selection_context::step_track(bool next) return umax; } + const std::string last_track = ::at32(playlist, current_track); + switch (repeat_mode) { case CELL_SEARCH_REPEATMODE_NONE: @@ -365,6 +367,12 @@ u32 music_selection_context::step_track(bool next) std::random_device rd; auto engine = std::default_random_engine{rd()}; std::shuffle(std::begin(playlist), std::end(playlist), engine); + + // Don't play the same track twice + if (last_track == ::at32(playlist, current_track)) + { + current_track = (current_track + 1) % playlist.size(); + } } } diff --git a/rpcs3/Emu/Cell/Modules/cellSearch.cpp b/rpcs3/Emu/Cell/Modules/cellSearch.cpp index bcc0151764..f86584d7c5 100644 --- a/rpcs3/Emu/Cell/Modules/cellSearch.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSearch.cpp @@ -1711,12 +1711,6 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrcontent_ids.begin(), searchObject->content_ids.end(), [&content_hash](const content_id_type& cid){ return cid.first == content_hash; }); if (content != searchObject->content_ids.cend() && content->second) { - // Check if the type of the found content is correct - if (content->second->type != CELL_SEARCH_CONTENTTYPE_MUSIC) - { - return { CELL_SEARCH_ERROR_INVALID_CONTENTTYPE, "Type: %d, Expected: CELL_SEARCH_CONTENTTYPE_MUSIC"}; - } - // Check if the type of the found content matches our search content type if (content->second->type != first_content->type) { @@ -1724,8 +1718,36 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptrsecond->infoPath.contentPath); - cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path=%s", content_hash, +content->second->type, context.playlist.back()); + if (content->second->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) + { + const std::string path = content->second->infoPath.contentPath; + const std::string vfs_path = vfs::get(path); + if (!fs::is_dir(vfs_path)) + { + return { CELL_SEARCH_ERROR_CONTENT_NOT_FOUND, "Not a directory: Path='%s'", vfs_path }; + } + + for (auto&& dir_entry : fs::dir{vfs_path}) + { + if (dir_entry.name == "." || dir_entry.name == "..") + { + continue; + } + + std::string track = path + "/" + dir_entry.name; + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path='%s'", content_hash, +content->second->type, track); + context.playlist.push_back(std::move(track)); + } + } + else if (content->second->type == CELL_SEARCH_CONTENTTYPE_MUSIC) + { + context.playlist.push_back(content->second->infoPath.contentPath); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path='%s'", content_hash, +content->second->type, context.playlist.back()); + } + else + { + return { CELL_SEARCH_ERROR_INVALID_CONTENTTYPE, "Type: %d, Expected: CELL_SEARCH_CONTENTTYPE_MUSIC or CELL_SEARCH_CONTENTTYPE_MUSICLIST", +content->second->type }; + } } else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) { @@ -1738,14 +1760,14 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr content = get_random_content(); context.playlist.push_back(content->infoPath.contentPath); - cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning random track: Type=0x%x, Path=%s", content_hash, +content->type, context.playlist.back()); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning random track: Type=0x%x, Path='%s'", content_hash, +content->type, context.playlist.back()); } else { // Select the first track by default // TODO: whole playlist context.playlist.push_back(first_content->infoPath.contentPath); - cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path=%s", content_hash, +first_content->type, context.playlist.back()); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path='%s'", content_hash, +first_content->type, context.playlist.back()); } } else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST) @@ -1759,14 +1781,14 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr content = get_random_content(); context.playlist.push_back(content->infoPath.contentPath); - cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning random track: Type=0x%x, Path=%s", +content->type, context.playlist.back()); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning random track: Type=0x%x, Path='%s'", +content->type, context.playlist.back()); } else { // Select the first track by default // TODO: whole playlist context.playlist.push_back(first_content->infoPath.contentPath); - cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path=%s", +first_content->type, context.playlist.back()); + cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path='%s'", +first_content->type, context.playlist.back()); } context.content_type = first_content->type; diff --git a/rpcs3/Emu/Io/Null/null_music_handler.h b/rpcs3/Emu/Io/Null/null_music_handler.h index fe022dc79e..fcf2bf0f1c 100644 --- a/rpcs3/Emu/Io/Null/null_music_handler.h +++ b/rpcs3/Emu/Io/Null/null_music_handler.h @@ -7,11 +7,11 @@ class null_music_handler final : public music_handler_base public: null_music_handler() : music_handler_base() {} - void stop() override { m_state = 0; } // CELL_MUSIC_PB_STATUS_STOP - void pause() override { m_state = 2; } // CELL_MUSIC_PB_STATUS_PAUSE - void play(const std::string& /*path*/) override { m_state = 1; } // CELL_MUSIC_PB_STATUS_PLAY - void fast_forward(const std::string& /*path*/) override { m_state = 3; } // CELL_MUSIC_PB_STATUS_FASTFORWARD - void fast_reverse(const std::string& /*path*/) override { m_state = 4; } // CELL_MUSIC_PB_STATUS_FASTREVERSE + void stop() override { set_state(0); } // CELL_MUSIC_PB_STATUS_STOP + void pause() override { set_state(2); } // CELL_MUSIC_PB_STATUS_PAUSE + void play(const std::string& /*path*/) override { set_state(1); } // CELL_MUSIC_PB_STATUS_PLAY + void fast_forward(const std::string& /*path*/) override { set_state(3); } // CELL_MUSIC_PB_STATUS_FASTFORWARD + void fast_reverse(const std::string& /*path*/) override { set_state(4); } // CELL_MUSIC_PB_STATUS_FASTREVERSE void set_volume(f32 volume) override { m_volume = volume; } f32 get_volume() const override { return m_volume; } diff --git a/rpcs3/Emu/Io/music_handler_base.h b/rpcs3/Emu/Io/music_handler_base.h index 8af59a938d..2fddeab359 100644 --- a/rpcs3/Emu/Io/music_handler_base.h +++ b/rpcs3/Emu/Io/music_handler_base.h @@ -21,17 +21,35 @@ public: virtual void set_volume(f32 volume) = 0; virtual f32 get_volume() const = 0; - s32 get_state() const + void set_state(u32 state) + { + m_state = state; + + if (m_event_status_callback) + { + m_event_status_callback(state); + } + } + + u32 get_state() const { return m_state; } - void set_status_callback(std::function status_callback) + void set_event_status_callback(std::function status_callback) { - m_status_callback = std::move(status_callback); + m_event_status_callback = std::move(status_callback); } + void set_playback_status_callback(std::function status_callback) + { + m_playback_status_callback = std::move(status_callback); + } + +private: + atomic_t m_state{0}; + std::function m_event_status_callback; + protected: - atomic_t m_state{0}; - std::function m_status_callback; + std::function m_playback_status_callback; }; diff --git a/rpcs3/rpcs3qt/qt_music_handler.cpp b/rpcs3/rpcs3qt/qt_music_handler.cpp index e2a3b79df8..6152e9c098 100644 --- a/rpcs3/rpcs3qt/qt_music_handler.cpp +++ b/rpcs3/rpcs3qt/qt_music_handler.cpp @@ -97,7 +97,7 @@ void qt_music_handler::stop() m_media_player->stop(); }); - m_state = CELL_MUSIC_PB_STATUS_STOP; + set_state(CELL_MUSIC_PB_STATUS_STOP); } void qt_music_handler::pause() @@ -110,7 +110,7 @@ void qt_music_handler::pause() m_media_player->pause(); }); - m_state = CELL_MUSIC_PB_STATUS_PAUSE; + set_state(CELL_MUSIC_PB_STATUS_PAUSE); } void qt_music_handler::play(const std::string& path) @@ -135,7 +135,7 @@ void qt_music_handler::play(const std::string& path) m_media_player->play(); }); - m_state = CELL_MUSIC_PB_STATUS_PLAY; + set_state(CELL_MUSIC_PB_STATUS_PLAY); } void qt_music_handler::fast_forward(const std::string& path) @@ -160,7 +160,7 @@ void qt_music_handler::fast_forward(const std::string& path) m_media_player->play(); }); - m_state = CELL_MUSIC_PB_STATUS_FASTFORWARD; + set_state(CELL_MUSIC_PB_STATUS_FASTFORWARD); } void qt_music_handler::fast_reverse(const std::string& path) @@ -185,7 +185,7 @@ void qt_music_handler::fast_reverse(const std::string& path) m_media_player->play(); }); - m_state = CELL_MUSIC_PB_STATUS_FASTREVERSE; + set_state(CELL_MUSIC_PB_STATUS_FASTREVERSE); } void qt_music_handler::set_volume(f32 volume) @@ -218,7 +218,7 @@ void qt_music_handler::handle_media_status(QMediaPlayer::MediaStatus status) { music_log.notice("New media status: %s (status=%d)", status, static_cast(status)); - if (!m_status_callback) + if (!m_playback_status_callback) { return; } @@ -234,7 +234,7 @@ void qt_music_handler::handle_media_status(QMediaPlayer::MediaStatus status) case QMediaPlayer::MediaStatus::InvalidMedia: break; case QMediaPlayer::MediaStatus::EndOfMedia: - m_status_callback(player_status::end_of_media); + m_playback_status_callback(player_status::end_of_media); break; default: music_log.error("Ignoring unknown status %d", static_cast(status)); diff --git a/rpcs3/util/media_utils.cpp b/rpcs3/util/media_utils.cpp index 284f6eb758..2a7a41e2f2 100644 --- a/rpcs3/util/media_utils.cpp +++ b/rpcs3/util/media_utils.cpp @@ -720,7 +720,8 @@ namespace utils { // Shuffle once if necessary media_log.notice("audio_decoder: shuffling initial playlist..."); - auto engine = std::default_random_engine{}; + std::random_device rd; + auto engine = std::default_random_engine{rd()}; std::shuffle(std::begin(m_context.playlist), std::end(m_context.playlist), engine); }