442 lines
12 KiB
C++
442 lines
12 KiB
C++
#include "AudioPlayerAsync.h"
|
|
#include <iostream>
|
|
#include "utils/Utils.h"
|
|
|
|
#ifdef __ANDROID__
|
|
#include <android/log.h>
|
|
#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<std::mutex> 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<std::mutex> 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<std::mutex> 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<char> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(mtx);
|
|
taskQueue.push([]() {
|
|
Mix_HaltMusic();
|
|
});
|
|
cv.notify_one();
|
|
#endif
|
|
|
|
}
|
|
|
|
void AudioPlayerAsync::pauseMusicAsync() {
|
|
if (!initialized) return;
|
|
#ifdef __EMSCRIPTEN__
|
|
Mix_PauseMusic();
|
|
#else
|
|
std::unique_lock<std::mutex> lock(mtx);
|
|
taskQueue.push([]() {
|
|
Mix_PauseMusic();
|
|
});
|
|
cv.notify_one();
|
|
#endif
|
|
|
|
}
|
|
|
|
void AudioPlayerAsync::resumeMusicAsync() {
|
|
if (!initialized) return;
|
|
#ifdef __EMSCRIPTEN__
|
|
Mix_ResumeMusic();
|
|
#else
|
|
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<void()> task;
|
|
{
|
|
std::unique_lock<std::mutex> lock(mtx);
|
|
cv.wait(lock, [this] { return !taskQueue.empty() || stop; });
|
|
if (stop && taskQueue.empty())
|
|
break;
|
|
task = std::move(taskQueue.front());
|
|
taskQueue.pop();
|
|
}
|
|
task();
|
|
}
|
|
}
|
|
#endif
|