#include "AudioPlayerAsync.h" #include #include "utils/Utils.h" #ifdef __ANDROID__ #include #endif namespace FRG { extern const char* CONST_MUSIC_ZIP_FILE; extern const char* CONST_ZIP_FILE; } #ifdef __EMSCRIPTEN__ AudioPlayerAsync::AudioPlayerAsync() {} #else AudioPlayerAsync::AudioPlayerAsync() : worker(&AudioPlayerAsync::workerThread, this) {} #endif AudioPlayerAsync::~AudioPlayerAsync() { #ifndef __EMSCRIPTEN__ { std::unique_lock lock(mtx); stop = true; cv.notify_all(); } if (worker.joinable()) worker.join(); #endif shutdown(); } bool AudioPlayerAsync::init() { if (initialized) return true; if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { FRG::logger() << "SDL_InitSubSystem(SDL_INIT_AUDIO) failed: " << SDL_GetError() << std::endl; return false; } if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) { FRG::logger() << "Mix_OpenAudio failed: " << Mix_GetError() << std::endl; return false; } Mix_AllocateChannels(16); initialized = true; FRG::logger() << "AudioPlayerAsync initialized with SDL2_mixer" << std::endl; return true; } void AudioPlayerAsync::shutdown() { if (!initialized) return; if (currentMusic_) { Mix_HaltMusic(); Mix_FreeMusic(currentMusic_); currentMusic_ = nullptr; } currentMusicData_.clear(); { std::lock_guard lock(soundCacheMutex); for (auto& pair : soundCache) { Mix_FreeChunk(pair.second); } soundCache.clear(); } Mix_CloseAudio(); SDL_QuitSubSystem(SDL_INIT_AUDIO); initialized = false; FRG::logger() << "AudioPlayerAsync shutdown" << std::endl; } void AudioPlayerAsync::playSoundAsync(const std::string& filePath, int loops, int channel) { if (!initialized) { FRG::logger() << "AudioPlayerAsync not initialized" << std::endl; return; } if (!soundEnabled_) return; auto task = [this, filePath, loops, channel]() { Mix_Chunk* sound = nullptr; { std::lock_guard cacheLock(soundCacheMutex); auto it = soundCache.find(filePath); if (it != soundCache.end()) { sound = it->second; } } if (!sound) { //#ifdef __ANDROID__ //Sounds are loaded from regular resource file std::vector buffer = FRG::readFileFromZIP(filePath, FRG::CONST_ZIP_FILE); if (buffer.empty()) { FRG::logger() << "Failed to read sound file into buffer: " << filePath << std::endl; return; } SDL_RWops* rw = SDL_RWFromMem(buffer.data(), buffer.size()); if (!rw) { FRG::logger() << "Failed to create RWops from buffer for " << filePath << ": " << SDL_GetError() << std::endl; return; } sound = Mix_LoadWAV_RW(rw, 1); // 1 means it will free rw automatically //FRG::logger() << "sound = Mix_LoadWAV_RW(rw, 1); just fired " << std::endl; //#else // sound = Mix_LoadWAV(filePath.c_str()); //#endif if (!sound) { FRG::logger() << "Failed to load sound " << filePath << ": " << Mix_GetError() << std::endl; return; } std::lock_guard cacheLock(soundCacheMutex); soundCache[filePath] = sound; } int result = Mix_PlayChannel(channel, sound, loops); if (result == -1) { FRG::logger() << "Mix_PlayChannel failed: " << Mix_GetError() << std::endl; } }; #ifdef __EMSCRIPTEN__ task(); #else std::unique_lock lock(mtx); taskQueue.push(std::move(task)); cv.notify_one(); #endif } void AudioPlayerAsync::playMusicAsync(const std::string& filePath, int loops) { if (!initialized) return; if (filePath == currentTrack) return; currentTrack = filePath; if (!musicEnabled_) return; if ((strlen(FRG::CONST_MUSIC_ZIP_FILE)>0) && !FRG::zipFileExists(FRG::CONST_MUSIC_ZIP_FILE)) { return; } auto task = [this, filePath, loops]() { Mix_HaltMusic(); if (currentMusic_) { Mix_FreeMusic(currentMusic_); currentMusic_ = nullptr; } currentMusicData_.clear(); Mix_Music* music = nullptr; //#ifdef __ANDROID__ currentMusicData_ = FRG::readFileFromZIP(filePath, FRG::CONST_MUSIC_ZIP_FILE); if (!currentMusicData_.empty()) { SDL_RWops* rw = SDL_RWFromMem(currentMusicData_.data(), currentMusicData_.size()); if (rw) { music = Mix_LoadMUS_RW(rw, 1); } } //#else // music = Mix_LoadMUS(filePath.c_str()); //#endif if (!music) { FRG::logger() << "Failed to load music " << filePath << ": " << Mix_GetError() << std::endl; return; } if (Mix_PlayMusic(music, loops) == -1) { FRG::logger() << "Mix_PlayMusic failed: " << Mix_GetError() << std::endl; Mix_FreeMusic(music); currentMusicData_.clear(); return; } currentMusic_ = music; }; #ifdef __EMSCRIPTEN__ task(); #else std::unique_lock lock(mtx); taskQueue.push(std::move(task)); cv.notify_one(); #endif } void AudioPlayerAsync::stopMusicAsync() { if (!initialized) return; currentTrack = ""; #ifdef __EMSCRIPTEN__ Mix_HaltMusic(); #else std::unique_lock lock(mtx); taskQueue.push([]() { Mix_HaltMusic(); }); cv.notify_one(); #endif } void AudioPlayerAsync::pauseMusicAsync() { if (!initialized) return; #ifdef __EMSCRIPTEN__ Mix_PauseMusic(); #else std::unique_lock lock(mtx); taskQueue.push([]() { Mix_PauseMusic(); }); cv.notify_one(); #endif } void AudioPlayerAsync::resumeMusicAsync() { if (!initialized) return; #ifdef __EMSCRIPTEN__ Mix_ResumeMusic(); #else std::unique_lock lock(mtx); taskQueue.push([]() { Mix_ResumeMusic(); }); cv.notify_one(); #endif } void AudioPlayerAsync::setMusicVolume(int volume) { if (!initialized) return; volume = std::max(0, std::min(128, volume)); musicVolume_ = volume; #ifdef __EMSCRIPTEN__ Mix_VolumeMusic(volume); #else std::unique_lock lock(mtx); taskQueue.push([volume]() { Mix_VolumeMusic(volume); }); cv.notify_one(); #endif } void AudioPlayerAsync::setSoundVolume(int volume) { if (!initialized) return; volume = std::max(0, std::min(128, volume)); soundVolume_ = volume; #ifdef __EMSCRIPTEN__ Mix_Volume(-1, volume); #else std::unique_lock lock(mtx); taskQueue.push([volume]() { Mix_Volume(-1, volume); // все каналы }); cv.notify_one(); #endif } void AudioPlayerAsync::crossFadeMusicAsync(const std::string& filePath, int fadeDurationMs, int loops) { FRG::logger() << "crossFadeMusicAsync called step 1" << std::endl; if (!initialized) return; FRG::logger() << "crossFadeMusicAsync called step 1.1" << std::endl; if ((strlen(FRG::CONST_MUSIC_ZIP_FILE) > 0) && !FRG::zipFileExists(FRG::CONST_MUSIC_ZIP_FILE)) { FRG::logger() << "crossFadeMusicAsync called step 1.2" << std::endl; return; } FRG::logger() << "crossFadeMusicAsync called step 1.3" << std::endl; if (filePath == currentTrack) return; FRG::logger() << "crossFadeMusicAsync called step 1.4" << std::endl; currentTrack = filePath; if (!musicEnabled_) return; FRG::logger() << "crossFadeMusicAsync called step 2" << std::endl; FRG::logger() << "crossFadeMusicAsync called step 3" << std::endl; auto task = [this, filePath, fadeDurationMs, loops]() { #ifdef __EMSCRIPTEN__ Mix_HaltMusic(); if (currentMusic_) { Mix_FreeMusic(currentMusic_); currentMusic_ = nullptr; } #else if (Mix_PlayingMusic()) { Mix_FadeOutMusic(fadeDurationMs / 2); SDL_Delay(fadeDurationMs / 2 + 20); } Mix_HaltMusic(); if (currentMusic_) { Mix_FreeMusic(currentMusic_); currentMusic_ = nullptr; } currentMusicData_.clear(); #endif Mix_Music* music = nullptr; //#ifdef __ANDROID__ currentMusicData_ = FRG::readFileFromZIP(filePath, FRG::CONST_MUSIC_ZIP_FILE); if (!currentMusicData_.empty()) { SDL_RWops* rw = SDL_RWFromMem(currentMusicData_.data(), currentMusicData_.size()); if (rw) { music = Mix_LoadMUS_RW(rw, 1); } } //#else // music = Mix_LoadMUS(filePath.c_str()); //#endif if (!music) { FRG::logger() << "Failed to load music " << filePath << ": " << Mix_GetError() << std::endl; return; } if (Mix_FadeInMusic(music, loops, fadeDurationMs / 2) == -1) { FRG::logger() << "Mix_FadeInMusic failed: " << Mix_GetError() << std::endl; Mix_FreeMusic(music); return; } currentMusic_ = music; }; #ifdef __EMSCRIPTEN__ task(); #else std::unique_lock lock(mtx); taskQueue.push(std::move(task)); cv.notify_one(); #endif } void AudioPlayerAsync::crossFadeMusicFromPositionAsync(const std::string& filePath, int fadeDurationMs, int loops) { if (!initialized) return; if (filePath == currentTrack) return; currentTrack = filePath; if (!musicEnabled_) return; auto task = [this, filePath, fadeDurationMs, loops]() { double position = 0.0; if (currentMusic_ && Mix_PlayingMusic()) { double pos = Mix_GetMusicPosition(currentMusic_); if (pos >= 0.0) position = pos; } #ifndef __EMSCRIPTEN__ if (Mix_PlayingMusic()) { Mix_FadeOutMusic(fadeDurationMs / 2); SDL_Delay(fadeDurationMs / 2 + 20); } #endif Mix_HaltMusic(); if (currentMusic_) { Mix_FreeMusic(currentMusic_); currentMusic_ = nullptr; } Mix_Music* music = Mix_LoadMUS(filePath.c_str()); if (!music) { FRG::logger() << "Failed to load music " << filePath << ": " << Mix_GetError() << std::endl; return; } if (Mix_FadeInMusicPos(music, loops, fadeDurationMs / 2, position) == -1) { FRG::logger() << "Mix_FadeInMusicPos failed: " << Mix_GetError() << std::endl; Mix_FreeMusic(music); return; } currentMusic_ = music; }; #ifdef __EMSCRIPTEN__ task(); #else std::unique_lock lock(mtx); taskQueue.push(std::move(task)); cv.notify_one(); #endif } void AudioPlayerAsync::setMusicEnabled(bool enabled) { if (musicEnabled_ == enabled) return; musicEnabled_ = enabled; if (!initialized) return; if (!enabled) { // Halt playback but keep currentTrack so we can restart it on re-enable. #ifdef __EMSCRIPTEN__ Mix_HaltMusic(); #else std::unique_lock lock(mtx); taskQueue.push([]() { Mix_HaltMusic(); }); cv.notify_one(); #endif } else { // Restart the last requested track, if any. if (!currentTrack.empty()) { const std::string track = currentTrack; currentTrack = ""; // clear so crossFadeMusicAsync doesn't bail early crossFadeMusicAsync(track); } } } void AudioPlayerAsync::setSoundEnabled(bool enabled) { soundEnabled_ = enabled; } #ifndef __EMSCRIPTEN__ void AudioPlayerAsync::workerThread() { while (true) { std::function task; { std::unique_lock lock(mtx); cv.wait(lock, [this] { return !taskQueue.empty() || stop; }); if (stop && taskQueue.empty()) break; task = std::move(taskQueue.front()); taskQueue.pop(); } task(); } } #endif