1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-25 22:42:04 +02:00

Reorganized music, video and sound players:

- All XXXplayers are now in client/media directory
- Reogranized code on one class per file basis
- Extracted interfaces from handlers. Handlers now implement
corresponding interface.
- CCS now only stores pointer to an interface
This commit is contained in:
Ivan Savenko
2024-05-02 15:55:20 +03:00
parent e00046dd70
commit d27b854cb1
51 changed files with 1284 additions and 1122 deletions

View File

@@ -36,21 +36,21 @@ class CMap;
VCMI_LIB_NAMESPACE_END
class CMapHandler;
class CSoundHandler;
class CMusicHandler;
class ISoundPlayer;
class IMusicPlayer;
class CursorHandler;
class IMainVideoPlayer;
class IVideoPlayer;
class CServerHandler;
//a class for non-mechanical client GUI classes
class CClientState
{
public:
CSoundHandler * soundh;
CMusicHandler * musich;
ISoundPlayer * soundh;
IMusicPlayer * musich;
CConsoleHandler * consoleh;
CursorHandler * curh;
IMainVideoPlayer * videoh;
IVideoPlayer * videoh;
};
extern CClientState * CCS;

View File

@@ -14,11 +14,13 @@
#include "CGameInfo.h"
#include "mainmenu/CMainMenu.h"
#include "media/CEmptyVideoPlayer.h"
#include "media/CMusicHandler.h"
#include "media/CSoundHandler.h"
#include "media/CVideoHandler.h"
#include "gui/CursorHandler.h"
#include "eventsSDL/InputHandler.h"
#include "CPlayerInterface.h"
#include "CVideoHandler.h"
#include "CMusicHandler.h"
#include "gui/CGuiHandler.h"
#include "gui/WindowHandler.h"
#include "CServerHandler.h"
@@ -292,10 +294,8 @@ int main(int argc, char * argv[])
{
//initializing audio
CCS->soundh = new CSoundHandler();
CCS->soundh->init();
CCS->soundh->setVolume((ui32)settings["general"]["sound"].Float());
CCS->musich = new CMusicHandler();
CCS->musich->init();
CCS->musich->setVolume((ui32)settings["general"]["music"].Float());
logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
}
@@ -457,9 +457,6 @@ static void mainLoop()
// cleanup, mostly to remove false leaks from analyzer
if(CCS)
{
CCS->musich->release();
CCS->soundh->release();
delete CCS->consoleh;
delete CCS->curh;
delete CCS->videoh;

View File

@@ -75,6 +75,11 @@ set(client_SRCS
mapView/MapViewModel.cpp
mapView/mapHandler.cpp
media/CAudioBase.cpp
media/CMusicHandler.cpp
media/CSoundHandler.cpp
media/CVideoHandler.cpp
render/CAnimation.cpp
render/CBitmapHandler.cpp
render/CDefFile.cpp
@@ -163,11 +168,9 @@ set(client_SRCS
CGameInfo.cpp
CMT.cpp
CMusicHandler.cpp
CPlayerInterface.cpp
PlayerLocalState.cpp
CServerHandler.cpp
CVideoHandler.cpp
Client.cpp
ClientCommandManager.cpp
GameChatHandler.cpp
@@ -260,6 +263,15 @@ set(client_HEADERS
mapView/MapViewModel.h
mapView/mapHandler.h
media/CAudioBase.h
media/CEmptyVideoPlayer.h
media/CMusicHandler.h
media/CSoundHandler.h
media/CVideoHandler.h
media/IMusicPlayer.h
media/ISoundPlayer.h
media/IVideoPlayer.h
render/CAnimation.h
render/CBitmapHandler.h
render/CDefFile.h
@@ -357,11 +369,9 @@ set(client_HEADERS
CGameInfo.h
CMT.h
CMusicHandler.h
CPlayerInterface.h
PlayerLocalState.h
CServerHandler.h
CVideoHandler.h
Client.h
ClientCommandManager.h
ClientNetPackVisitors.h

View File

@@ -1,753 +0,0 @@
/*
* CMusicHandler.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include <SDL_mixer.h>
#include <SDL_timer.h>
#include "CMusicHandler.h"
#include "CGameInfo.h"
#include "renderSDL/SDLRWwrapper.h"
#include "eventsSDL/InputHandler.h"
#include "gui/CGuiHandler.h"
#include "../lib/GameConstants.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/constants/StringConstants.h"
#include "../lib/CRandomGenerator.h"
#include "../lib/VCMIDirs.h"
#include "../lib/TerrainHandler.h"
#define VCMI_SOUND_NAME(x)
#define VCMI_SOUND_FILE(y) #y,
// sounds mapped to soundBase enum
static const std::string sounds[] = {
"", // invalid
"", // todo
VCMI_SOUND_LIST
};
#undef VCMI_SOUND_NAME
#undef VCMI_SOUND_FILE
void CAudioBase::init()
{
if (initialized)
return;
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024)==-1)
{
logGlobal->error("Mix_OpenAudio error: %s", Mix_GetError());
return;
}
initialized = true;
}
void CAudioBase::release()
{
if(!(CCS->soundh->initialized && CCS->musich->initialized))
Mix_CloseAudio();
initialized = false;
}
void CAudioBase::setVolume(ui32 percent)
{
if (percent > 100)
percent = 100;
volume = percent;
}
void CSoundHandler::onVolumeChange(const JsonNode &volumeNode)
{
setVolume((ui32)volumeNode.Float());
}
CSoundHandler::CSoundHandler():
listener(settings.listen["general"]["sound"]),
ambientConfig(JsonPath::builtin("config/ambientSounds.json"))
{
listener(std::bind(&CSoundHandler::onVolumeChange, this, _1));
battleIntroSounds =
{
soundBase::battle00, soundBase::battle01,
soundBase::battle02, soundBase::battle03, soundBase::battle04,
soundBase::battle05, soundBase::battle06, soundBase::battle07
};
}
void CSoundHandler::init()
{
CAudioBase::init();
if(ambientConfig["allocateChannels"].isNumber())
Mix_AllocateChannels((int)ambientConfig["allocateChannels"].Integer());
if (initialized)
{
Mix_ChannelFinished([](int channel)
{
CCS->soundh->soundFinishedCallback(channel);
});
}
}
void CSoundHandler::release()
{
if (initialized)
{
Mix_HaltChannel(-1);
for (auto &chunk : soundChunks)
{
if (chunk.second.first)
Mix_FreeChunk(chunk.second.first);
}
}
CAudioBase::release();
}
// Allocate an SDL chunk and cache it.
Mix_Chunk *CSoundHandler::GetSoundChunk(const AudioPath & sound, bool cache)
{
try
{
if (cache && soundChunks.find(sound) != soundChunks.end())
return soundChunks[sound].first;
auto data = CResourceHandler::get()->load(sound.addPrefix("SOUNDS/"))->readAll();
SDL_RWops *ops = SDL_RWFromMem(data.first.get(), (int)data.second);
Mix_Chunk *chunk = Mix_LoadWAV_RW(ops, 1); // will free ops
if (cache)
soundChunks.insert({sound, std::make_pair (chunk, std::move (data.first))});
return chunk;
}
catch(std::exception &e)
{
logGlobal->warn("Cannot get sound %s chunk: %s", sound.getOriginalName(), e.what());
return nullptr;
}
}
Mix_Chunk *CSoundHandler::GetSoundChunk(std::pair<std::unique_ptr<ui8 []>, si64> & data, bool cache)
{
try
{
std::vector<ui8> startBytes = std::vector<ui8>(data.first.get(), data.first.get() + std::min((si64)100, data.second));
if (cache && soundChunksRaw.find(startBytes) != soundChunksRaw.end())
return soundChunksRaw[startBytes].first;
SDL_RWops *ops = SDL_RWFromMem(data.first.get(), (int)data.second);
Mix_Chunk *chunk = Mix_LoadWAV_RW(ops, 1); // will free ops
if (cache)
soundChunksRaw.insert({startBytes, std::make_pair (chunk, std::move (data.first))});
return chunk;
}
catch(std::exception &e)
{
logGlobal->warn("Cannot get sound chunk: %s", e.what());
return nullptr;
}
}
int CSoundHandler::ambientDistToVolume(int distance) const
{
const auto & distancesVector = ambientConfig["distances"].Vector();
if(distance >= distancesVector.size())
return 0;
int volume = static_cast<int>(distancesVector[distance].Integer());
return volume * (int)ambientConfig["volume"].Integer() / 100;
}
void CSoundHandler::ambientStopSound(const AudioPath & soundId)
{
stopSound(ambientChannels[soundId]);
setChannelVolume(ambientChannels[soundId], volume);
}
uint32_t CSoundHandler::getSoundDurationMilliseconds(const AudioPath & sound)
{
if (!initialized || sound.empty())
return 0;
auto resourcePath = sound.addPrefix("SOUNDS/");
if (!CResourceHandler::get()->existsResource(resourcePath))
return 0;
auto data = CResourceHandler::get()->load(resourcePath)->readAll();
SDL_AudioSpec spec;
uint32_t audioLen;
uint8_t *audioBuf;
uint32_t miliseconds = 0;
if(SDL_LoadWAV_RW(SDL_RWFromMem(data.first.get(), (int)data.second), 1, &spec, &audioBuf, &audioLen) != nullptr)
{
SDL_FreeWAV(audioBuf);
uint32_t sampleSize = SDL_AUDIO_BITSIZE(spec.format) / 8;
uint32_t sampleCount = audioLen / sampleSize;
uint32_t sampleLen = sampleCount / spec.channels;
miliseconds = 1000 * sampleLen / spec.freq;
}
return miliseconds ;
}
// Plays a sound, and return its channel so we can fade it out later
int CSoundHandler::playSound(soundBase::soundID soundID, int repeats)
{
assert(soundID < soundBase::sound_after_last);
auto sound = AudioPath::builtin(sounds[soundID]);
logGlobal->trace("Attempt to play sound %d with file name %s with cache", soundID, sound.getOriginalName());
return playSound(sound, repeats, true);
}
int CSoundHandler::playSound(const AudioPath & sound, int repeats, bool cache)
{
if (!initialized || sound.empty())
return -1;
int channel;
Mix_Chunk *chunk = GetSoundChunk(sound, cache);
if (chunk)
{
channel = Mix_PlayChannel(-1, chunk, repeats);
if (channel == -1)
{
logGlobal->error("Unable to play sound file %s , error %s", sound.getOriginalName(), Mix_GetError());
if (!cache)
Mix_FreeChunk(chunk);
}
else if (cache)
initCallback(channel);
else
initCallback(channel, [chunk](){ Mix_FreeChunk(chunk);});
}
else
channel = -1;
return channel;
}
int CSoundHandler::playSound(std::pair<std::unique_ptr<ui8 []>, si64> & data, int repeats, bool cache)
{
int channel = -1;
if (Mix_Chunk *chunk = GetSoundChunk(data, cache))
{
channel = Mix_PlayChannel(-1, chunk, repeats);
if (channel == -1)
{
logGlobal->error("Unable to play sound, error %s", Mix_GetError());
if (!cache)
Mix_FreeChunk(chunk);
}
else if (cache)
initCallback(channel);
else
initCallback(channel, [chunk](){ Mix_FreeChunk(chunk);});
}
return channel;
}
// Helper. Randomly select a sound from an array and play it
int CSoundHandler::playSoundFromSet(std::vector<soundBase::soundID> &sound_vec)
{
return playSound(*RandomGeneratorUtil::nextItem(sound_vec, CRandomGenerator::getDefault()));
}
void CSoundHandler::stopSound(int handler)
{
if (initialized && handler != -1)
Mix_HaltChannel(handler);
}
// Sets the sound volume, from 0 (mute) to 100
void CSoundHandler::setVolume(ui32 percent)
{
CAudioBase::setVolume(percent);
if (initialized)
{
setChannelVolume(-1, volume);
for (auto const & channel : channelVolumes)
updateChannelVolume(channel.first);
}
}
void CSoundHandler::updateChannelVolume(int channel)
{
if (channelVolumes.count(channel))
setChannelVolume(channel, getVolume() * channelVolumes[channel] / 100);
else
setChannelVolume(channel, getVolume());
}
// Sets the sound volume, from 0 (mute) to 100
void CSoundHandler::setChannelVolume(int channel, ui32 percent)
{
Mix_Volume(channel, (MIX_MAX_VOLUME * percent)/100);
}
void CSoundHandler::setCallback(int channel, std::function<void()> function)
{
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
auto iter = callbacks.find(channel);
//channel not found. It may have finished so fire callback now
if(iter == callbacks.end())
function();
else
iter->second.push_back(function);
}
void CSoundHandler::resetCallback(int channel)
{
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
callbacks.erase(channel);
}
void CSoundHandler::soundFinishedCallback(int channel)
{
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
if (callbacks.count(channel) == 0)
return;
// store callbacks from container locally - SDL might reuse this channel for another sound
// but do actualy execution in separate thread, to avoid potential deadlocks in case if callback requires locks of its own
auto callback = callbacks.at(channel);
callbacks.erase(channel);
if (!callback.empty())
{
GH.dispatchMainThread([callback](){
for (auto entry : callback)
entry();
});
}
}
void CSoundHandler::initCallback(int channel)
{
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
assert(callbacks.count(channel) == 0);
callbacks[channel] = {};
}
void CSoundHandler::initCallback(int channel, const std::function<void()> & function)
{
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
assert(callbacks.count(channel) == 0);
callbacks[channel].push_back(function);
}
int CSoundHandler::ambientGetRange() const
{
return static_cast<int>(ambientConfig["range"].Integer());
}
void CSoundHandler::ambientUpdateChannels(std::map<AudioPath, int> soundsArg)
{
boost::mutex::scoped_lock guard(mutex);
std::vector<AudioPath> stoppedSounds;
for(auto & pair : ambientChannels)
{
const auto & soundId = pair.first;
const int channel = pair.second;
if(!vstd::contains(soundsArg, soundId))
{
ambientStopSound(soundId);
stoppedSounds.push_back(soundId);
}
else
{
int volume = ambientDistToVolume(soundsArg[soundId]);
channelVolumes[channel] = volume;
updateChannelVolume(channel);
}
}
for(auto soundId : stoppedSounds)
{
channelVolumes.erase(ambientChannels[soundId]);
ambientChannels.erase(soundId);
}
for(auto & pair : soundsArg)
{
const auto & soundId = pair.first;
const int distance = pair.second;
if(!vstd::contains(ambientChannels, soundId))
{
int channel = playSound(soundId, -1);
int volume = ambientDistToVolume(distance);
channelVolumes[channel] = volume;
updateChannelVolume(channel);
ambientChannels[soundId] = channel;
}
}
}
void CSoundHandler::ambientStopAllChannels()
{
boost::mutex::scoped_lock guard(mutex);
for(auto ch : ambientChannels)
{
ambientStopSound(ch.first);
}
channelVolumes.clear();
ambientChannels.clear();
}
void CMusicHandler::onVolumeChange(const JsonNode &volumeNode)
{
setVolume((ui32)volumeNode.Float());
}
CMusicHandler::CMusicHandler():
listener(settings.listen["general"]["music"])
{
listener(std::bind(&CMusicHandler::onVolumeChange, this, _1));
auto mp3files = CResourceHandler::get()->getFilteredFiles([](const ResourcePath & id) -> bool
{
if(id.getType() != EResType::SOUND)
return false;
if(!boost::algorithm::istarts_with(id.getName(), "MUSIC/"))
return false;
logGlobal->trace("Found music file %s", id.getName());
return true;
});
for(const ResourcePath & file : mp3files)
{
if(boost::algorithm::istarts_with(file.getName(), "MUSIC/Combat"))
addEntryToSet("battle", AudioPath::fromResource(file));
else if(boost::algorithm::istarts_with(file.getName(), "MUSIC/AITheme"))
addEntryToSet("enemy-turn", AudioPath::fromResource(file));
}
}
void CMusicHandler::loadTerrainMusicThemes()
{
for (const auto & terrain : CGI->terrainTypeHandler->objects)
{
addEntryToSet("terrain_" + terrain->getJsonKey(), terrain->musicFilename);
}
}
void CMusicHandler::addEntryToSet(const std::string & set, const AudioPath & musicURI)
{
musicsSet[set].push_back(musicURI);
}
void CMusicHandler::init()
{
CAudioBase::init();
if (initialized)
{
Mix_HookMusicFinished([]()
{
CCS->musich->musicFinishedCallback();
});
}
}
void CMusicHandler::release()
{
if (initialized)
{
boost::mutex::scoped_lock guard(mutex);
Mix_HookMusicFinished(nullptr);
current->stop();
current.reset();
next.reset();
}
CAudioBase::release();
}
void CMusicHandler::playMusic(const AudioPath & musicURI, bool loop, bool fromStart)
{
boost::mutex::scoped_lock guard(mutex);
if (current && current->isPlaying() && current->isTrack(musicURI))
return;
queueNext(this, "", musicURI, loop, fromStart);
}
void CMusicHandler::playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart)
{
playMusicFromSet(musicSet + "_" + entryID, loop, fromStart);
}
void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop, bool fromStart)
{
boost::mutex::scoped_lock guard(mutex);
auto selectedSet = musicsSet.find(whichSet);
if (selectedSet == musicsSet.end())
{
logGlobal->error("Error: playing music from non-existing set: %s", whichSet);
return;
}
if (current && current->isPlaying() && current->isSet(whichSet))
return;
// in this mode - play random track from set
queueNext(this, whichSet, AudioPath(), loop, fromStart);
}
void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
{
if (!initialized)
return;
next = std::move(queued);
if (current.get() == nullptr || !current->stop(1000))
{
current.reset(next.release());
current->play();
}
}
void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const AudioPath & musicURI, bool looped, bool fromStart)
{
queueNext(std::make_unique<MusicEntry>(owner, setName, musicURI, looped, fromStart));
}
void CMusicHandler::stopMusic(int fade_ms)
{
if (!initialized)
return;
boost::mutex::scoped_lock guard(mutex);
if (current.get() != nullptr)
current->stop(fade_ms);
next.reset();
}
void CMusicHandler::setVolume(ui32 percent)
{
CAudioBase::setVolume(percent);
if (initialized)
Mix_VolumeMusic((MIX_MAX_VOLUME * volume)/100);
}
void CMusicHandler::musicFinishedCallback()
{
// call music restart in separate thread to avoid deadlock in some cases
// It is possible for:
// 1) SDL thread to call this method on end of playback
// 2) VCMI code to call queueNext() method to queue new file
// this leads to:
// 1) SDL thread waiting to acquire music lock in this method (while keeping internal SDL mutex locked)
// 2) VCMI thread waiting to acquire internal SDL mutex (while keeping music mutex locked)
GH.dispatchMainThread([this]()
{
boost::unique_lock lockGuard(mutex);
if (current.get() != nullptr)
{
// if music is looped, play it again
if (current->play())
return;
else
current.reset();
}
if (current.get() == nullptr && next.get() != nullptr)
{
current.reset(next.release());
current->play();
}
});
}
MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart):
owner(owner),
music(nullptr),
playing(false),
startTime(uint32_t(-1)),
startPosition(0),
loop(looped ? -1 : 1),
fromStart(fromStart),
setName(std::move(setName))
{
if (!musicURI.empty())
load(std::move(musicURI));
}
MusicEntry::~MusicEntry()
{
if (playing && loop > 0)
{
assert(0);
logGlobal->error("Attempt to delete music while playing!");
Mix_HaltMusic();
}
if (loop == 0 && Mix_FadingMusic() != MIX_NO_FADING)
{
assert(0);
logGlobal->error("Attempt to delete music while fading out!");
Mix_HaltMusic();
}
logGlobal->trace("Del-ing music file %s", currentName.getOriginalName());
if (music)
Mix_FreeMusic(music);
}
void MusicEntry::load(const AudioPath & musicURI)
{
if (music)
{
logGlobal->trace("Del-ing music file %s", currentName.getOriginalName());
Mix_FreeMusic(music);
music = nullptr;
}
if (CResourceHandler::get()->existsResource(musicURI))
currentName = musicURI;
else
currentName = musicURI.addPrefix("MUSIC/");
music = nullptr;
logGlobal->trace("Loading music file %s", currentName.getOriginalName());
try
{
auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(currentName));
music = Mix_LoadMUS_RW(musicFile, SDL_TRUE);
}
catch(std::exception &e)
{
logGlobal->error("Failed to load music. setName=%s\tmusicURI=%s", setName, currentName.getOriginalName());
logGlobal->error("Exception: %s", e.what());
}
if(!music)
{
logGlobal->warn("Warning: Cannot open %s: %s", currentName.getOriginalName(), Mix_GetError());
return;
}
}
bool MusicEntry::play()
{
if (!(loop--) && music) //already played once - return
return false;
if (!setName.empty())
{
const auto & set = owner->musicsSet[setName];
const auto & iter = RandomGeneratorUtil::nextItem(set, CRandomGenerator::getDefault());
load(*iter);
}
logGlobal->trace("Playing music file %s", currentName.getOriginalName());
if (!fromStart && owner->trackPositions.count(currentName) > 0 && owner->trackPositions[currentName] > 0)
{
float timeToStart = owner->trackPositions[currentName];
startPosition = std::round(timeToStart * 1000);
// erase stored position:
// if music track will be interrupted again - new position will be written in stop() method
// if music track is not interrupted and will finish by timeout/end of file - it will restart from begginning as it should
owner->trackPositions.erase(owner->trackPositions.find(currentName));
if (Mix_FadeInMusicPos(music, 1, 1000, timeToStart) == -1)
{
logGlobal->error("Unable to play music (%s)", Mix_GetError());
return false;
}
}
else
{
startPosition = 0;
if(Mix_PlayMusic(music, 1) == -1)
{
logGlobal->error("Unable to play music (%s)", Mix_GetError());
return false;
}
}
startTime = GH.input().getTicks();
playing = true;
return true;
}
bool MusicEntry::stop(int fade_ms)
{
if (Mix_PlayingMusic())
{
playing = false;
loop = 0;
uint32_t endTime = GH.input().getTicks();
assert(startTime != uint32_t(-1));
float playDuration = (endTime - startTime + startPosition) / 1000.f;
owner->trackPositions[currentName] = playDuration;
logGlobal->trace("Stopping music file %s at %f", currentName.getOriginalName(), playDuration);
Mix_FadeOutMusic(fade_ms);
return true;
}
return false;
}
bool MusicEntry::isPlaying()
{
return playing;
}
bool MusicEntry::isSet(std::string set)
{
return !setName.empty() && set == setName;
}
bool MusicEntry::isTrack(const AudioPath & track)
{
return setName.empty() && track == currentName;
}

View File

@@ -1,170 +0,0 @@
/*
* CMusicHandler.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../lib/CConfigHandler.h"
#include "../lib/CSoundBase.h"
struct _Mix_Music;
struct SDL_RWops;
using Mix_Music = struct _Mix_Music;
struct Mix_Chunk;
class CAudioBase {
protected:
boost::mutex mutex;
bool initialized;
int volume; // from 0 (mute) to 100
CAudioBase(): initialized(false), volume(0) {};
~CAudioBase() = default;
public:
virtual void init() = 0;
virtual void release() = 0;
virtual void setVolume(ui32 percent);
ui32 getVolume() const { return volume; };
};
class CSoundHandler final : public CAudioBase
{
private:
//update volume on configuration change
SettingsListener listener;
void onVolumeChange(const JsonNode &volumeNode);
using CachedChunk = std::pair<Mix_Chunk *, std::unique_ptr<ui8[]>>;
std::map<AudioPath, CachedChunk> soundChunks;
std::map<std::vector<ui8>, CachedChunk> soundChunksRaw;
Mix_Chunk *GetSoundChunk(const AudioPath & sound, bool cache);
Mix_Chunk *GetSoundChunk(std::pair<std::unique_ptr<ui8 []>, si64> & data, bool cache);
/// have entry for every currently active channel
/// vector will be empty if callback was not set
std::map<int, std::vector<std::function<void()>> > callbacks;
/// Protects access to callbacks member to avoid data races:
/// SDL calls sound finished callbacks from audio thread
boost::mutex mutexCallbacks;
int ambientDistToVolume(int distance) const;
void ambientStopSound(const AudioPath & soundId);
void updateChannelVolume(int channel);
const JsonNode ambientConfig;
std::map<AudioPath, int> ambientChannels;
std::map<int, int> channelVolumes;
void initCallback(int channel, const std::function<void()> & function);
void initCallback(int channel);
public:
CSoundHandler();
void init() override;
void release() override;
void setVolume(ui32 percent) override;
void setChannelVolume(int channel, ui32 percent);
// Sounds
uint32_t getSoundDurationMilliseconds(const AudioPath & sound);
int playSound(soundBase::soundID soundID, int repeats=0);
int playSound(const AudioPath & sound, int repeats=0, bool cache=false);
int playSound(std::pair<std::unique_ptr<ui8 []>, si64> & data, int repeats=0, bool cache=false);
int playSoundFromSet(std::vector<soundBase::soundID> &sound_vec);
void stopSound(int handler);
void setCallback(int channel, std::function<void()> function);
void resetCallback(int channel);
void soundFinishedCallback(int channel);
int ambientGetRange() const;
void ambientUpdateChannels(std::map<AudioPath, int> currentSounds);
void ambientStopAllChannels();
// Sets
std::vector<soundBase::soundID> battleIntroSounds;
};
class CMusicHandler;
//Class for handling one music file
class MusicEntry
{
CMusicHandler *owner;
Mix_Music *music;
int loop; // -1 = indefinite
bool fromStart;
bool playing;
uint32_t startTime;
uint32_t startPosition;
//if not null - set from which music will be randomly selected
std::string setName;
AudioPath currentName;
void load(const AudioPath & musicURI);
public:
MusicEntry(CMusicHandler *owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart);
~MusicEntry();
bool isSet(std::string setName);
bool isTrack(const AudioPath & trackName);
bool isPlaying();
bool play();
bool stop(int fade_ms=0);
};
class CMusicHandler final: public CAudioBase
{
private:
//update volume on configuration change
SettingsListener listener;
void onVolumeChange(const JsonNode &volumeNode);
std::unique_ptr<MusicEntry> current;
std::unique_ptr<MusicEntry> next;
void queueNext(CMusicHandler *owner, const std::string & setName, const AudioPath & musicURI, bool looped, bool fromStart);
void queueNext(std::unique_ptr<MusicEntry> queued);
void musicFinishedCallback();
/// map <set name> -> <list of URI's to tracks belonging to the said set>
std::map<std::string, std::vector<AudioPath>> musicsSet;
/// stored position, in seconds at which music player should resume playing this track
std::map<AudioPath, float> trackPositions;
public:
CMusicHandler();
/// add entry with URI musicURI in set. Track will have ID musicID
void addEntryToSet(const std::string & set, const AudioPath & musicURI);
void init() override;
void loadTerrainMusicThemes();
void release() override;
void setVolume(ui32 percent) override;
/// play track by URI, if loop = true music will be looped
void playMusic(const AudioPath & musicURI, bool loop, bool fromStart);
/// play random track from this set
void playMusicFromSet(const std::string & musicSet, bool loop, bool fromStart);
/// play random track from set (musicSet, entryID)
void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart);
/// stops currently playing music by fading out it over fade_ms and starts next scheduled track, if any
void stopMusic(int fade_ms=1000);
friend class MusicEntry;
};

View File

@@ -14,7 +14,6 @@
#include "CGameInfo.h"
#include "CMT.h"
#include "CMusicHandler.h"
#include "CServerHandler.h"
#include "HeroMovementController.h"
#include "PlayerLocalState.h"
@@ -41,6 +40,9 @@
#include "mapView/mapHandler.h"
#include "media/IMusicPlayer.h"
#include "media/ISoundPlayer.h"
#include "render/CAnimation.h"
#include "render/IImage.h"
#include "render/IRenderHandler.h"

View File

@@ -1,131 +0,0 @@
/*
* CVideoHandler.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../lib/Rect.h"
#include "../lib/filesystem/ResourcePath.h"
struct SDL_Surface;
struct SDL_Texture;
enum class EVideoType : ui8
{
INTRO = 0, // use entire window: stopOnKey = true, scale = true, overlay = false
SPELLBOOK // overlay video: stopOnKey = false, scale = false, overlay = true
};
class IVideoPlayer : boost::noncopyable
{
public:
virtual bool open(const VideoPath & name, bool scale = false)=0; //true - succes
virtual void close()=0;
virtual bool nextFrame()=0;
virtual void show(int x, int y, SDL_Surface *dst, bool update = true)=0;
virtual void redraw(int x, int y, SDL_Surface *dst, bool update = true)=0; //reblits buffer
virtual bool wait()=0;
virtual int curFrame() const =0;
virtual int frameCount() const =0;
};
class IMainVideoPlayer : public IVideoPlayer
{
public:
virtual ~IMainVideoPlayer() = default;
virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> restart = nullptr){}
virtual bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType)
{
return false;
}
virtual std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) { return std::make_pair(nullptr, 0); };
virtual Point size() { return Point(0, 0); };
};
class CEmptyVideoPlayer final : public IMainVideoPlayer
{
public:
int curFrame() const override {return -1;};
int frameCount() const override {return -1;};
void redraw( int x, int y, SDL_Surface *dst, bool update = true ) override {};
void show( int x, int y, SDL_Surface *dst, bool update = true ) override {};
bool nextFrame() override {return false;};
void close() override {};
bool wait() override {return false;};
bool open(const VideoPath & name, bool scale = false) override {return false;};
};
#ifndef DISABLE_VIDEO
struct AVFormatContext;
struct AVCodecContext;
struct AVCodec;
struct AVFrame;
struct AVIOContext;
VCMI_LIB_NAMESPACE_BEGIN
class CInputStream;
VCMI_LIB_NAMESPACE_END
class CVideoPlayer final : public IMainVideoPlayer
{
int stream; // stream index in video
AVFormatContext *format;
AVCodecContext *codecContext; // codec context for stream
const AVCodec *codec;
AVFrame *frame;
struct SwsContext *sws;
AVIOContext * context;
VideoPath fname; //name of current video file (empty if idle)
// Destination. Either overlay or dest.
SDL_Texture *texture;
SDL_Surface *dest;
Rect destRect; // valid when dest is used
Rect pos; // destination on screen
/// video playback currnet progress, in seconds
double frameTime;
bool doLoop; // loop through video
bool playVideo(int x, int y, bool stopOnKey, bool overlay);
bool open(const VideoPath & fname, bool loop, bool useOverlay = false, bool scale = false);
public:
CVideoPlayer();
~CVideoPlayer();
bool init();
bool open(const VideoPath & fname, bool scale = false) override;
void close() override;
bool nextFrame() override; // display next frame
void show(int x, int y, SDL_Surface *dst, bool update = true) override; //blit current frame
void redraw(int x, int y, SDL_Surface *dst, bool update = true) override; //reblits buffer
void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> onVideoRestart = nullptr) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true
// Opens video, calls playVideo, closes video; returns playVideo result (if whole video has been played)
bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType) override;
std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) override;
Point size() override;
//TODO:
bool wait() override {return false;};
int curFrame() const override {return -1;};
int frameCount() const override {return -1;};
// public to allow access from ffmpeg IO functions
std::unique_ptr<CInputStream> data;
std::unique_ptr<CInputStream> dataAudio;
};
#endif

View File

@@ -11,7 +11,6 @@
#include "HeroMovementController.h"
#include "CGameInfo.h"
#include "CMusicHandler.h"
#include "CPlayerInterface.h"
#include "PlayerLocalState.h"
#include "adventureMap/AdventureMapInterface.h"
@@ -19,10 +18,12 @@
#include "gui/CGuiHandler.h"
#include "gui/CursorHandler.h"
#include "mapView/mapHandler.h"
#include "media/ISoundPlayer.h"
#include "../CCallback.h"
#include "../lib/CondSh.h"
#include "../lib/CConfigHandler.h"
#include "../lib/pathfinder/CGPathNode.h"
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/networkPacks/PacksForClient.h"

View File

@@ -12,7 +12,6 @@
#include "CInGameConsole.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../CServerHandler.h"
#include "../GameChatHandler.h"
@@ -21,6 +20,7 @@
#include "../gui/WindowHandler.h"
#include "../gui/Shortcut.h"
#include "../gui/TextAlignment.h"
#include "../media/ISoundPlayer.h"
#include "../render/Colors.h"
#include "../render/Canvas.h"
#include "../render/IScreenHandler.h"

View File

@@ -20,11 +20,11 @@
#include "../widgets/MiscWidgets.h"
#include "../windows/InfoWindows.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../media/ISoundPlayer.h"
#include "../render/IScreenHandler.h"
#include "../../CCallback.h"

View File

@@ -12,9 +12,10 @@
#include "../CCallback.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../mapView/mapHandler.h"
#include "../media/IMusicPlayer.h"
#include "../media/ISoundPlayer.h"
#include "../../lib/TerrainHandler.h"
#include "../../lib/mapObjects/CArmedInstance.h"

View File

@@ -11,11 +11,11 @@
#include "TurnTimerWidget.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../battle/BattleInterface.h"
#include "../battle/BattleStacksController.h"
#include "../gui/CGuiHandler.h"
#include "../media/ISoundPlayer.h"
#include "../render/Graphics.h"
#include "../widgets/Images.h"
#include "../widgets/GraphicalPrimitiveCanvas.h"

View File

@@ -20,10 +20,10 @@
#include "CreatureAnimation.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../media/ISoundPlayer.h"
#include "../render/IRenderHandler.h"
#include "../../CCallback.h"

View File

@@ -18,9 +18,9 @@
#include "BattleStacksController.h"
#include "BattleRenderer.h"
#include "../CMusicHandler.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../media/ISoundPlayer.h"
#include "../render/Canvas.h"
#include "../render/CAnimation.h"
#include "../render/Graphics.h"

View File

@@ -24,11 +24,12 @@
#include "BattleRenderer.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../media/IMusicPlayer.h"
#include "../media/ISoundPlayer.h"
#include "../windows/CTutorialWindow.h"
#include "../render/Canvas.h"
#include "../adventureMap/AdventureMapInterface.h"
@@ -113,7 +114,14 @@ void BattleInterface::playIntroSoundAndUnlockInterface()
onIntroSoundPlayed();
};
int battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
std::vector<soundBase::soundID> battleIntroSounds =
{
soundBase::battle00, soundBase::battle01,
soundBase::battle02, soundBase::battle03, soundBase::battle04,
soundBase::battle05, soundBase::battle06, soundBase::battle07
};
int battleIntroSoundChannel = CCS->soundh->playSoundFromSet(battleIntroSounds);
if (battleIntroSoundChannel != -1)
{
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);

View File

@@ -19,14 +19,14 @@
#include "BattleWindow.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../CVideoHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../gui/MouseButton.h"
#include "../gui/WindowHandler.h"
#include "../media/IMusicPlayer.h"
#include "../media/IVideoPlayer.h"
#include "../render/Canvas.h"
#include "../render/IImage.h"
#include "../render/IFont.h"

View File

@@ -17,10 +17,10 @@
#include "BattleRenderer.h"
#include "CreatureAnimation.h"
#include "../CMusicHandler.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CGuiHandler.h"
#include "../media/ISoundPlayer.h"
#include "../render/Canvas.h"
#include "../render/IRenderHandler.h"

View File

@@ -17,10 +17,10 @@
#include "BattleFieldController.h"
#include "BattleRenderer.h"
#include "../CMusicHandler.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CGuiHandler.h"
#include "../media/ISoundPlayer.h"
#include "../render/Canvas.h"
#include "../render/IImage.h"
#include "../render/IRenderHandler.h"

View File

@@ -23,10 +23,10 @@
#include "CreatureAnimation.h"
#include "../CPlayerInterface.h"
#include "../CMusicHandler.h"
#include "../CGameInfo.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../media/ISoundPlayer.h"
#include "../render/Colors.h"
#include "../render/Canvas.h"
#include "../render/IRenderHandler.h"

View File

@@ -18,7 +18,6 @@
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../CMusicHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"

View File

@@ -22,10 +22,11 @@
#include "../gui/CursorHandler.h"
#include "../gui/EventDispatcher.h"
#include "../gui/MouseButton.h"
#include "../media/IMusicPlayer.h"
#include "../media/ISoundPlayer.h"
#include "../CMT.h"
#include "../CPlayerInterface.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../../lib/CConfigHandler.h"

View File

@@ -17,11 +17,11 @@
#include "GlobalLobbyWindow.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../mainmenu/CMainMenu.h"
#include "../media/ISoundPlayer.h"
#include "../windows/InfoWindows.h"
#include "../../lib/CConfigHandler.h"

View File

@@ -16,10 +16,10 @@
#include "GlobalLobbyRoomWindow.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../media/ISoundPlayer.h"
#include "../render/Colors.h"
#include "../widgets/Buttons.h"
#include "../widgets/CTextInput.h"

View File

@@ -18,12 +18,11 @@
#include "ExtraOptionsTab.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../CPlayerInterface.h"
#include "../CServerHandler.h"
#include "../mainmenu/CMainMenu.h"
#include "../mainmenu/CPrologEpilogVideo.h"
#include "../media/IMusicPlayer.h"
#include "../widgets/CComponent.h"
#include "../widgets/Buttons.h"
#include "../widgets/MiscWidgets.h"
@@ -41,9 +40,9 @@
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CBuildingHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CSkillHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/CHeroHandler.h"

View File

@@ -20,14 +20,13 @@
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../gui/WindowHandler.h"
#include "../globalLobby/GlobalLobbyClient.h"
#include "../mainmenu/CMainMenu.h"
#include "../media/ISoundPlayer.h"
#include "../widgets/Buttons.h"
#include "../widgets/CComponent.h"
#include "../widgets/CTextInput.h"

View File

@@ -14,12 +14,12 @@
#include "../CGameInfo.h"
#include "../CServerHandler.h"
#include "../CMusicHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../gui/WindowHandler.h"
#include "../render/Graphics.h"
#include "../render/IFont.h"
#include "../media/ISoundPlayer.h"
#include "../widgets/CComponent.h"
#include "../widgets/ComboBox.h"
#include "../widgets/CTextInput.h"
@@ -38,6 +38,7 @@
#include "../../lib/networkPacks/PacksForLobby.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CArtHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/mapping/CMapInfo.h"

View File

@@ -14,12 +14,12 @@
#include "CMainMenu.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../CPlayerInterface.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../media/IMusicPlayer.h"
#include "../media/IVideoPlayer.h"
#include "../render/Canvas.h"
#include "../widgets/CComponent.h"
#include "../widgets/Buttons.h"
@@ -36,7 +36,7 @@
#include "../../lib/CArtHandler.h"
#include "../../lib/CBuildingHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CSkillHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/CHeroHandler.h"

View File

@@ -14,6 +14,9 @@
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../gui/Shortcut.h"
#include "../media/IMusicPlayer.h"
#include "../media/ISoundPlayer.h"
#include "../media/IVideoPlayer.h"
#include "../widgets/Buttons.h"
#include "../widgets/CTextInput.h"
#include "../widgets/Images.h"
@@ -23,8 +26,6 @@
#include "../render/Canvas.h"
#include "../CGameInfo.h"
#include "../CVideoHandler.h"
#include "../CMusicHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CCreatureHandler.h"

View File

@@ -17,6 +17,8 @@
#include "../lobby/CBonusSelection.h"
#include "../lobby/CSelectionBase.h"
#include "../lobby/CLobbyScreen.h"
#include "../media/IMusicPlayer.h"
#include "../media/IVideoPlayer.h"
#include "../gui/CursorHandler.h"
#include "../windows/GUIClasses.h"
#include "../gui/CGuiHandler.h"
@@ -37,8 +39,6 @@
#include "../CServerHandler.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../CPlayerInterface.h"
#include "../Client.h"
#include "../CMT.h"

View File

@@ -12,8 +12,9 @@
#include "CPrologEpilogVideo.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../media/IMusicPlayer.h"
#include "../media/ISoundPlayer.h"
#include "../media/IVideoPlayer.h"
#include "../gui/WindowHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/FramerateManager.h"

View File

@@ -0,0 +1,45 @@
/*
* CAudioBase.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "CAudioBase.h"
#include "../CGameInfo.h"
#include <SDL_mixer.h>
int CAudioBase::initializationCounter = 0;
bool CAudioBase::initializeSuccess = false;
CAudioBase::CAudioBase()
{
if(initializationCounter == 0)
{
if(Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024) == -1)
logGlobal->error("Mix_OpenAudio error: %s", Mix_GetError());
else
initializeSuccess = true;
}
++initializationCounter;
}
bool CAudioBase::isInitialized() const
{
return initializeSuccess;
}
CAudioBase::~CAudioBase()
{
--initializationCounter;
if(initializationCounter == 0 && initializeSuccess)
Mix_CloseAudio();
initializeSuccess = false;
}

21
client/media/CAudioBase.h Normal file
View File

@@ -0,0 +1,21 @@
/*
* CAudioBase.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
class CAudioBase
{
static int initializationCounter;
static bool initializeSuccess;
protected:
bool isInitialized() const;
CAudioBase();
~CAudioBase();
};

View File

@@ -0,0 +1,30 @@
/*
* CEmptyVideoPlayer.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "IVideoPlayer.h"
#include "../lib/Point.h"
class CEmptyVideoPlayer final : public IVideoPlayer
{
public:
int curFrame() const override {return -1;};
int frameCount() const override {return -1;};
void redraw( int x, int y, SDL_Surface *dst, bool update) override {};
void show( int x, int y, SDL_Surface *dst, bool update) override {};
bool nextFrame() override {return false;};
void close() override {};
bool wait() override {return false;};
bool open(const VideoPath & name, bool scale) override {return false;};
void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update, std::function<void()> restart) override {}
bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType) override { return false; }
std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) override { return std::make_pair(nullptr, 0); };
Point size() override { return Point(0, 0); };
};

View File

@@ -0,0 +1,346 @@
/*
* CMusicHandler.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "CMusicHandler.h"
#include "../CGameInfo.h"
#include "../renderSDL/SDLRWwrapper.h"
#include "../eventsSDL/InputHandler.h"
#include "../gui/CGuiHandler.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/CRandomGenerator.h"
#include "../../lib/TerrainHandler.h"
#include <SDL_mixer.h>
void CMusicHandler::onVolumeChange(const JsonNode &volumeNode)
{
setVolume((ui32)volumeNode.Float());
}
CMusicHandler::CMusicHandler():
listener(settings.listen["general"]["music"])
{
listener(std::bind(&CMusicHandler::onVolumeChange, this, _1));
auto mp3files = CResourceHandler::get()->getFilteredFiles([](const ResourcePath & id) -> bool
{
if(id.getType() != EResType::SOUND)
return false;
if(!boost::algorithm::istarts_with(id.getName(), "MUSIC/"))
return false;
logGlobal->trace("Found music file %s", id.getName());
return true;
});
for(const ResourcePath & file : mp3files)
{
if(boost::algorithm::istarts_with(file.getName(), "MUSIC/Combat"))
addEntryToSet("battle", AudioPath::fromResource(file));
else if(boost::algorithm::istarts_with(file.getName(), "MUSIC/AITheme"))
addEntryToSet("enemy-turn", AudioPath::fromResource(file));
}
if (isInitialized())
{
Mix_HookMusicFinished([]()
{
CCS->musich->musicFinishedCallback();
});
}
}
void CMusicHandler::loadTerrainMusicThemes()
{
for (const auto & terrain : CGI->terrainTypeHandler->objects)
{
addEntryToSet("terrain_" + terrain->getJsonKey(), terrain->musicFilename);
}
}
void CMusicHandler::addEntryToSet(const std::string & set, const AudioPath & musicURI)
{
musicsSet[set].push_back(musicURI);
}
CMusicHandler::~CMusicHandler()
{
if (isInitialized())
{
boost::mutex::scoped_lock guard(mutex);
Mix_HookMusicFinished(nullptr);
current->stop();
current.reset();
next.reset();
}
}
void CMusicHandler::playMusic(const AudioPath & musicURI, bool loop, bool fromStart)
{
boost::mutex::scoped_lock guard(mutex);
if (current && current->isPlaying() && current->isTrack(musicURI))
return;
queueNext(this, "", musicURI, loop, fromStart);
}
void CMusicHandler::playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart)
{
playMusicFromSet(musicSet + "_" + entryID, loop, fromStart);
}
void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop, bool fromStart)
{
boost::mutex::scoped_lock guard(mutex);
auto selectedSet = musicsSet.find(whichSet);
if (selectedSet == musicsSet.end())
{
logGlobal->error("Error: playing music from non-existing set: %s", whichSet);
return;
}
if (current && current->isPlaying() && current->isSet(whichSet))
return;
// in this mode - play random track from set
queueNext(this, whichSet, AudioPath(), loop, fromStart);
}
void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
{
if (!isInitialized())
return;
next = std::move(queued);
if (current.get() == nullptr || !current->stop(1000))
{
current.reset(next.release());
current->play();
}
}
void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const AudioPath & musicURI, bool looped, bool fromStart)
{
queueNext(std::make_unique<MusicEntry>(owner, setName, musicURI, looped, fromStart));
}
void CMusicHandler::stopMusic(int fade_ms)
{
if (!isInitialized())
return;
boost::mutex::scoped_lock guard(mutex);
if (current.get() != nullptr)
current->stop(fade_ms);
next.reset();
}
ui32 CMusicHandler::getVolume() const
{
return volume;
}
void CMusicHandler::setVolume(ui32 percent)
{
volume = std::min(100u, percent);
if (isInitialized())
Mix_VolumeMusic((MIX_MAX_VOLUME * volume)/100);
}
void CMusicHandler::musicFinishedCallback()
{
// call music restart in separate thread to avoid deadlock in some cases
// It is possible for:
// 1) SDL thread to call this method on end of playback
// 2) VCMI code to call queueNext() method to queue new file
// this leads to:
// 1) SDL thread waiting to acquire music lock in this method (while keeping internal SDL mutex locked)
// 2) VCMI thread waiting to acquire internal SDL mutex (while keeping music mutex locked)
GH.dispatchMainThread([this]()
{
boost::unique_lock lockGuard(mutex);
if (current.get() != nullptr)
{
// if music is looped, play it again
if (current->play())
return;
else
current.reset();
}
if (current.get() == nullptr && next.get() != nullptr)
{
current.reset(next.release());
current->play();
}
});
}
MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart):
owner(owner),
music(nullptr),
playing(false),
startTime(uint32_t(-1)),
startPosition(0),
loop(looped ? -1 : 1),
fromStart(fromStart),
setName(std::move(setName))
{
if (!musicURI.empty())
load(std::move(musicURI));
}
MusicEntry::~MusicEntry()
{
if (playing && loop > 0)
{
assert(0);
logGlobal->error("Attempt to delete music while playing!");
Mix_HaltMusic();
}
if (loop == 0 && Mix_FadingMusic() != MIX_NO_FADING)
{
assert(0);
logGlobal->error("Attempt to delete music while fading out!");
Mix_HaltMusic();
}
logGlobal->trace("Del-ing music file %s", currentName.getOriginalName());
if (music)
Mix_FreeMusic(music);
}
void MusicEntry::load(const AudioPath & musicURI)
{
if (music)
{
logGlobal->trace("Del-ing music file %s", currentName.getOriginalName());
Mix_FreeMusic(music);
music = nullptr;
}
if (CResourceHandler::get()->existsResource(musicURI))
currentName = musicURI;
else
currentName = musicURI.addPrefix("MUSIC/");
music = nullptr;
logGlobal->trace("Loading music file %s", currentName.getOriginalName());
try
{
auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(currentName));
music = Mix_LoadMUS_RW(musicFile, SDL_TRUE);
}
catch(std::exception &e)
{
logGlobal->error("Failed to load music. setName=%s\tmusicURI=%s", setName, currentName.getOriginalName());
logGlobal->error("Exception: %s", e.what());
}
if(!music)
{
logGlobal->warn("Warning: Cannot open %s: %s", currentName.getOriginalName(), Mix_GetError());
return;
}
}
bool MusicEntry::play()
{
if (!(loop--) && music) //already played once - return
return false;
if (!setName.empty())
{
const auto & set = owner->musicsSet[setName];
const auto & iter = RandomGeneratorUtil::nextItem(set, CRandomGenerator::getDefault());
load(*iter);
}
logGlobal->trace("Playing music file %s", currentName.getOriginalName());
if (!fromStart && owner->trackPositions.count(currentName) > 0 && owner->trackPositions[currentName] > 0)
{
float timeToStart = owner->trackPositions[currentName];
startPosition = std::round(timeToStart * 1000);
// erase stored position:
// if music track will be interrupted again - new position will be written in stop() method
// if music track is not interrupted and will finish by timeout/end of file - it will restart from begginning as it should
owner->trackPositions.erase(owner->trackPositions.find(currentName));
if (Mix_FadeInMusicPos(music, 1, 1000, timeToStart) == -1)
{
logGlobal->error("Unable to play music (%s)", Mix_GetError());
return false;
}
}
else
{
startPosition = 0;
if(Mix_PlayMusic(music, 1) == -1)
{
logGlobal->error("Unable to play music (%s)", Mix_GetError());
return false;
}
}
startTime = GH.input().getTicks();
playing = true;
return true;
}
bool MusicEntry::stop(int fade_ms)
{
if (Mix_PlayingMusic())
{
playing = false;
loop = 0;
uint32_t endTime = GH.input().getTicks();
assert(startTime != uint32_t(-1));
float playDuration = (endTime - startTime + startPosition) / 1000.f;
owner->trackPositions[currentName] = playDuration;
logGlobal->trace("Stopping music file %s at %f", currentName.getOriginalName(), playDuration);
Mix_FadeOutMusic(fade_ms);
return true;
}
return false;
}
bool MusicEntry::isPlaying()
{
return playing;
}
bool MusicEntry::isSet(std::string set)
{
return !setName.empty() && set == setName;
}
bool MusicEntry::isTrack(const AudioPath & track)
{
return setName.empty() && track == currentName;
}

View File

@@ -0,0 +1,94 @@
/*
* CMusicHandler.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CAudioBase.h"
#include "IMusicPlayer.h"
#include "../lib/CConfigHandler.h"
struct _Mix_Music;
using Mix_Music = struct _Mix_Music;
class CMusicHandler;
//Class for handling one music file
class MusicEntry
{
CMusicHandler *owner;
Mix_Music *music;
int loop; // -1 = indefinite
bool fromStart;
bool playing;
uint32_t startTime;
uint32_t startPosition;
//if not null - set from which music will be randomly selected
std::string setName;
AudioPath currentName;
void load(const AudioPath & musicURI);
public:
MusicEntry(CMusicHandler *owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart);
~MusicEntry();
bool isSet(std::string setName);
bool isTrack(const AudioPath & trackName);
bool isPlaying();
bool play();
bool stop(int fade_ms=0);
};
class CMusicHandler final: public CAudioBase, public IMusicPlayer
{
private:
//update volume on configuration change
SettingsListener listener;
void onVolumeChange(const JsonNode &volumeNode);
std::unique_ptr<MusicEntry> current;
std::unique_ptr<MusicEntry> next;
boost::mutex mutex;
int volume = 0; // from 0 (mute) to 100
void queueNext(CMusicHandler *owner, const std::string & setName, const AudioPath & musicURI, bool looped, bool fromStart);
void queueNext(std::unique_ptr<MusicEntry> queued);
void musicFinishedCallback() final;
/// map <set name> -> <list of URI's to tracks belonging to the said set>
std::map<std::string, std::vector<AudioPath>> musicsSet;
/// stored position, in seconds at which music player should resume playing this track
std::map<AudioPath, float> trackPositions;
public:
CMusicHandler();
~CMusicHandler();
/// add entry with URI musicURI in set. Track will have ID musicID
void addEntryToSet(const std::string & set, const AudioPath & musicURI);
void loadTerrainMusicThemes() final;
void setVolume(ui32 percent) final;
ui32 getVolume() const final;
/// play track by URI, if loop = true music will be looped
void playMusic(const AudioPath & musicURI, bool loop, bool fromStart) final;
/// play random track from this set
void playMusicFromSet(const std::string & musicSet, bool loop, bool fromStart) final;
/// play random track from set (musicSet, entryID)
void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart) final;
/// stops currently playing music by fading out it over fade_ms and starts next scheduled track, if any
void stopMusic(int fade_ms=1000) final;
friend class MusicEntry;
};

View File

@@ -0,0 +1,383 @@
/*
* CMusicHandler.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "CSoundHandler.h"
#include "../gui/CGuiHandler.h"
#include "../CGameInfo.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/CRandomGenerator.h"
#include <SDL_mixer.h>
#define VCMI_SOUND_NAME(x)
#define VCMI_SOUND_FILE(y) #y,
// sounds mapped to soundBase enum
static const std::string sounds[] = {
"", // invalid
"", // todo
VCMI_SOUND_LIST
};
#undef VCMI_SOUND_NAME
#undef VCMI_SOUND_FILE
void CSoundHandler::onVolumeChange(const JsonNode &volumeNode)
{
setVolume((ui32)volumeNode.Float());
}
CSoundHandler::CSoundHandler():
listener(settings.listen["general"]["sound"]),
ambientConfig(JsonPath::builtin("config/ambientSounds.json"))
{
listener(std::bind(&CSoundHandler::onVolumeChange, this, _1));
if(ambientConfig["allocateChannels"].isNumber())
Mix_AllocateChannels((int)ambientConfig["allocateChannels"].Integer());
if (isInitialized())
{
Mix_ChannelFinished([](int channel)
{
CCS->soundh->soundFinishedCallback(channel);
});
}
}
CSoundHandler::~CSoundHandler()
{
if (isInitialized())
{
Mix_HaltChannel(-1);
for (auto &chunk : soundChunks)
{
if (chunk.second.first)
Mix_FreeChunk(chunk.second.first);
}
}
}
// Allocate an SDL chunk and cache it.
Mix_Chunk *CSoundHandler::GetSoundChunk(const AudioPath & sound, bool cache)
{
try
{
if (cache && soundChunks.find(sound) != soundChunks.end())
return soundChunks[sound].first;
auto data = CResourceHandler::get()->load(sound.addPrefix("SOUNDS/"))->readAll();
SDL_RWops *ops = SDL_RWFromMem(data.first.get(), (int)data.second);
Mix_Chunk *chunk = Mix_LoadWAV_RW(ops, 1); // will free ops
if (cache)
soundChunks.insert({sound, std::make_pair (chunk, std::move (data.first))});
return chunk;
}
catch(std::exception &e)
{
logGlobal->warn("Cannot get sound %s chunk: %s", sound.getOriginalName(), e.what());
return nullptr;
}
}
Mix_Chunk *CSoundHandler::GetSoundChunk(std::pair<std::unique_ptr<ui8 []>, si64> & data, bool cache)
{
try
{
std::vector<ui8> startBytes = std::vector<ui8>(data.first.get(), data.first.get() + std::min((si64)100, data.second));
if (cache && soundChunksRaw.find(startBytes) != soundChunksRaw.end())
return soundChunksRaw[startBytes].first;
SDL_RWops *ops = SDL_RWFromMem(data.first.get(), (int)data.second);
Mix_Chunk *chunk = Mix_LoadWAV_RW(ops, 1); // will free ops
if (cache)
soundChunksRaw.insert({startBytes, std::make_pair (chunk, std::move (data.first))});
return chunk;
}
catch(std::exception &e)
{
logGlobal->warn("Cannot get sound chunk: %s", e.what());
return nullptr;
}
}
int CSoundHandler::ambientDistToVolume(int distance) const
{
const auto & distancesVector = ambientConfig["distances"].Vector();
if(distance >= distancesVector.size())
return 0;
int volume = static_cast<int>(distancesVector[distance].Integer());
return volume * (int)ambientConfig["volume"].Integer() / 100;
}
void CSoundHandler::ambientStopSound(const AudioPath & soundId)
{
stopSound(ambientChannels[soundId]);
setChannelVolume(ambientChannels[soundId], volume);
}
uint32_t CSoundHandler::getSoundDurationMilliseconds(const AudioPath & sound)
{
if (!isInitialized() || sound.empty())
return 0;
auto resourcePath = sound.addPrefix("SOUNDS/");
if (!CResourceHandler::get()->existsResource(resourcePath))
return 0;
auto data = CResourceHandler::get()->load(resourcePath)->readAll();
SDL_AudioSpec spec;
uint32_t audioLen;
uint8_t *audioBuf;
uint32_t miliseconds = 0;
if(SDL_LoadWAV_RW(SDL_RWFromMem(data.first.get(), (int)data.second), 1, &spec, &audioBuf, &audioLen) != nullptr)
{
SDL_FreeWAV(audioBuf);
uint32_t sampleSize = SDL_AUDIO_BITSIZE(spec.format) / 8;
uint32_t sampleCount = audioLen / sampleSize;
uint32_t sampleLen = sampleCount / spec.channels;
miliseconds = 1000 * sampleLen / spec.freq;
}
return miliseconds ;
}
// Plays a sound, and return its channel so we can fade it out later
int CSoundHandler::playSound(soundBase::soundID soundID, int repeats)
{
assert(soundID < soundBase::sound_after_last);
auto sound = AudioPath::builtin(sounds[soundID]);
logGlobal->trace("Attempt to play sound %d with file name %s with cache", soundID, sound.getOriginalName());
return playSound(sound, repeats, true);
}
int CSoundHandler::playSound(const AudioPath & sound, int repeats, bool cache)
{
if (!isInitialized() || sound.empty())
return -1;
int channel;
Mix_Chunk *chunk = GetSoundChunk(sound, cache);
if (chunk)
{
channel = Mix_PlayChannel(-1, chunk, repeats);
if (channel == -1)
{
logGlobal->error("Unable to play sound file %s , error %s", sound.getOriginalName(), Mix_GetError());
if (!cache)
Mix_FreeChunk(chunk);
}
else if (cache)
initCallback(channel);
else
initCallback(channel, [chunk](){ Mix_FreeChunk(chunk);});
}
else
channel = -1;
return channel;
}
int CSoundHandler::playSound(std::pair<std::unique_ptr<ui8 []>, si64> & data, int repeats, bool cache)
{
int channel = -1;
if (Mix_Chunk *chunk = GetSoundChunk(data, cache))
{
channel = Mix_PlayChannel(-1, chunk, repeats);
if (channel == -1)
{
logGlobal->error("Unable to play sound, error %s", Mix_GetError());
if (!cache)
Mix_FreeChunk(chunk);
}
else if (cache)
initCallback(channel);
else
initCallback(channel, [chunk](){ Mix_FreeChunk(chunk);});
}
return channel;
}
// Helper. Randomly select a sound from an array and play it
int CSoundHandler::playSoundFromSet(std::vector<soundBase::soundID> &sound_vec)
{
return playSound(*RandomGeneratorUtil::nextItem(sound_vec, CRandomGenerator::getDefault()));
}
void CSoundHandler::stopSound(int handler)
{
if (isInitialized() && handler != -1)
Mix_HaltChannel(handler);
}
ui32 CSoundHandler::getVolume() const
{
return volume;
}
// Sets the sound volume, from 0 (mute) to 100
void CSoundHandler::setVolume(ui32 percent)
{
volume = std::min(100u, percent);
if (isInitialized())
{
setChannelVolume(-1, volume);
for (auto const & channel : channelVolumes)
updateChannelVolume(channel.first);
}
}
void CSoundHandler::updateChannelVolume(int channel)
{
if (channelVolumes.count(channel))
setChannelVolume(channel, getVolume() * channelVolumes[channel] / 100);
else
setChannelVolume(channel, getVolume());
}
// Sets the sound volume, from 0 (mute) to 100
void CSoundHandler::setChannelVolume(int channel, ui32 percent)
{
Mix_Volume(channel, (MIX_MAX_VOLUME * percent)/100);
}
void CSoundHandler::setCallback(int channel, std::function<void()> function)
{
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
auto iter = callbacks.find(channel);
//channel not found. It may have finished so fire callback now
if(iter == callbacks.end())
function();
else
iter->second.push_back(function);
}
void CSoundHandler::resetCallback(int channel)
{
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
callbacks.erase(channel);
}
void CSoundHandler::soundFinishedCallback(int channel)
{
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
if (callbacks.count(channel) == 0)
return;
// store callbacks from container locally - SDL might reuse this channel for another sound
// but do actualy execution in separate thread, to avoid potential deadlocks in case if callback requires locks of its own
auto callback = callbacks.at(channel);
callbacks.erase(channel);
if (!callback.empty())
{
GH.dispatchMainThread([callback](){
for (auto entry : callback)
entry();
});
}
}
void CSoundHandler::initCallback(int channel)
{
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
assert(callbacks.count(channel) == 0);
callbacks[channel] = {};
}
void CSoundHandler::initCallback(int channel, const std::function<void()> & function)
{
boost::mutex::scoped_lock lockGuard(mutexCallbacks);
assert(callbacks.count(channel) == 0);
callbacks[channel].push_back(function);
}
int CSoundHandler::ambientGetRange() const
{
return static_cast<int>(ambientConfig["range"].Integer());
}
void CSoundHandler::ambientUpdateChannels(std::map<AudioPath, int> soundsArg)
{
boost::mutex::scoped_lock guard(mutex);
std::vector<AudioPath> stoppedSounds;
for(auto & pair : ambientChannels)
{
const auto & soundId = pair.first;
const int channel = pair.second;
if(!vstd::contains(soundsArg, soundId))
{
ambientStopSound(soundId);
stoppedSounds.push_back(soundId);
}
else
{
int volume = ambientDistToVolume(soundsArg[soundId]);
channelVolumes[channel] = volume;
updateChannelVolume(channel);
}
}
for(auto soundId : stoppedSounds)
{
channelVolumes.erase(ambientChannels[soundId]);
ambientChannels.erase(soundId);
}
for(auto & pair : soundsArg)
{
const auto & soundId = pair.first;
const int distance = pair.second;
if(!vstd::contains(ambientChannels, soundId))
{
int channel = playSound(soundId, -1);
int volume = ambientDistToVolume(distance);
channelVolumes[channel] = volume;
updateChannelVolume(channel);
ambientChannels[soundId] = channel;
}
}
}
void CSoundHandler::ambientStopAllChannels()
{
boost::mutex::scoped_lock guard(mutex);
for(auto ch : ambientChannels)
{
ambientStopSound(ch.first);
}
channelVolumes.clear();
ambientChannels.clear();
}

View File

@@ -0,0 +1,78 @@
/*
* CSoundHandler.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CAudioBase.h"
#include "ISoundPlayer.h"
#include "../lib/CConfigHandler.h"
struct Mix_Chunk;
class CSoundHandler final : public CAudioBase, public ISoundPlayer
{
private:
//update volume on configuration change
SettingsListener listener;
void onVolumeChange(const JsonNode &volumeNode);
using CachedChunk = std::pair<Mix_Chunk *, std::unique_ptr<ui8[]>>;
std::map<AudioPath, CachedChunk> soundChunks;
std::map<std::vector<ui8>, CachedChunk> soundChunksRaw;
Mix_Chunk *GetSoundChunk(const AudioPath & sound, bool cache);
Mix_Chunk *GetSoundChunk(std::pair<std::unique_ptr<ui8 []>, si64> & data, bool cache);
/// have entry for every currently active channel
/// vector will be empty if callback was not set
std::map<int, std::vector<std::function<void()>> > callbacks;
/// Protects access to callbacks member to avoid data races:
/// SDL calls sound finished callbacks from audio thread
boost::mutex mutexCallbacks;
int ambientDistToVolume(int distance) const;
void ambientStopSound(const AudioPath & soundId);
void updateChannelVolume(int channel);
const JsonNode ambientConfig;
boost::mutex mutex;
std::map<AudioPath, int> ambientChannels;
std::map<int, int> channelVolumes;
int volume = 0;
void initCallback(int channel, const std::function<void()> & function);
void initCallback(int channel);
public:
CSoundHandler();
~CSoundHandler();
ui32 getVolume() const final;
void setVolume(ui32 percent) final;
void setChannelVolume(int channel, ui32 percent);
// Sounds
uint32_t getSoundDurationMilliseconds(const AudioPath & sound) final;
int playSound(soundBase::soundID soundID, int repeats=0) final;
int playSound(const AudioPath & sound, int repeats=0, bool cache=false) final;
int playSound(std::pair<std::unique_ptr<ui8 []>, si64> & data, int repeats=0, bool cache=false) final;
int playSoundFromSet(std::vector<soundBase::soundID> &sound_vec) final;
void stopSound(int handler) final;
void setCallback(int channel, std::function<void()> function) final;
void resetCallback(int channel) final;
void soundFinishedCallback(int channel) final;
int ambientGetRange() const final;
void ambientUpdateChannels(std::map<AudioPath, int> currentSounds) final;
void ambientStopAllChannels() final;
};

View File

@@ -10,6 +10,8 @@
#include "StdInc.h"
#include "CVideoHandler.h"
#ifndef DISABLE_VIDEO
#include "CMT.h"
#include "gui/CGuiHandler.h"
#include "eventsSDL/InputHandler.h"
@@ -21,8 +23,6 @@
#include <SDL_render.h>
#ifndef DISABLE_VIDEO
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
@@ -43,7 +43,7 @@ static int lodRead(void* opaque, uint8_t* buf, int size)
auto video = reinterpret_cast<CVideoPlayer *>(opaque);
int bytes = static_cast<int>(video->data->read(buf, size));
if(bytes == 0)
return AVERROR_EOF;
return AVERROR_EOF;
return bytes;
}
@@ -64,7 +64,7 @@ static int lodReadAudio(void* opaque, uint8_t* buf, int size)
auto video = reinterpret_cast<CVideoPlayer *>(opaque);
int bytes = static_cast<int>(video->dataAudio->read(buf, size));
if(bytes == 0)
return AVERROR_EOF;
return AVERROR_EOF;
return bytes;
}
@@ -97,7 +97,7 @@ CVideoPlayer::CVideoPlayer()
bool CVideoPlayer::open(const VideoPath & fname, bool scale)
{
return open(fname, true, false);
return open(fname, true, false, false);
}
// loop = to loop through the video
@@ -395,7 +395,7 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo
if(onVideoRestart)
onVideoRestart();
VideoPath filenameToReopen = fname; // create copy to backup this->fname
open(filenameToReopen);
open(filenameToReopen, false);
nextFrame();
// The y position is wrong at the first frame.

View File

@@ -0,0 +1,86 @@
/*
* CVideoHandler.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#ifndef DISABLE_VIDEO
#include "IVideoPlayer.h"
#include "../lib/Rect.h"
struct SDL_Surface;
struct SDL_Texture;
struct AVFormatContext;
struct AVCodecContext;
struct AVCodec;
struct AVFrame;
struct AVIOContext;
VCMI_LIB_NAMESPACE_BEGIN
class CInputStream;
VCMI_LIB_NAMESPACE_END
class CVideoPlayer final : public IVideoPlayer
{
int stream; // stream index in video
AVFormatContext *format;
AVCodecContext *codecContext; // codec context for stream
const AVCodec *codec;
AVFrame *frame;
struct SwsContext *sws;
AVIOContext * context;
VideoPath fname; //name of current video file (empty if idle)
// Destination. Either overlay or dest.
SDL_Texture *texture;
SDL_Surface *dest;
Rect destRect; // valid when dest is used
Rect pos; // destination on screen
/// video playback currnet progress, in seconds
double frameTime;
bool doLoop; // loop through video
bool playVideo(int x, int y, bool stopOnKey, bool overlay);
bool open(const VideoPath & fname, bool loop, bool useOverlay, bool scale);
public:
CVideoPlayer();
~CVideoPlayer();
bool init();
bool open(const VideoPath & fname, bool scale) override;
void close() override;
bool nextFrame() override; // display next frame
void show(int x, int y, SDL_Surface *dst, bool update) override; //blit current frame
void redraw(int x, int y, SDL_Surface *dst, bool update) override; //reblits buffer
void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update, std::function<void()> onVideoRestart) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true
// Opens video, calls playVideo, closes video; returns playVideo result (if whole video has been played)
bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType) override;
std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) override;
Point size() override;
//TODO:
bool wait() override {return false;};
int curFrame() const override {return -1;};
int frameCount() const override {return -1;};
// public to allow access from ffmpeg IO functions
std::unique_ptr<CInputStream> data;
std::unique_ptr<CInputStream> dataAudio;
};
#endif

View File

@@ -0,0 +1,33 @@
/*
* IMusicPlayer.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../lib/filesystem/ResourcePath.h"
class IMusicPlayer
{
public:
virtual ~IMusicPlayer() = default;
virtual void loadTerrainMusicThemes() = 0;
virtual void setVolume(ui32 percent) = 0;
virtual ui32 getVolume() const = 0;
virtual void musicFinishedCallback() = 0;
/// play track by URI, if loop = true music will be looped
virtual void playMusic(const AudioPath & musicURI, bool loop, bool fromStart) = 0;
/// play random track from this set
virtual void playMusicFromSet(const std::string & musicSet, bool loop, bool fromStart) = 0;
/// play random track from set (musicSet, entryID)
virtual void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart) = 0;
/// stops currently playing music by fading out it over fade_ms and starts next scheduled track, if any
virtual void stopMusic(int fade_ms=1000) = 0;
};

View File

@@ -0,0 +1,35 @@
/*
* ISoundPlayer.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../lib/CSoundBase.h"
#include "../lib/filesystem/ResourcePath.h"
class ISoundPlayer
{
public:
virtual ~ISoundPlayer() = default;
virtual int playSound(soundBase::soundID soundID, int repeats = 0) = 0;
virtual int playSound(const AudioPath & sound, int repeats = 0, bool cache = false) = 0;
virtual int playSound(std::pair<std::unique_ptr<ui8[]>, si64> & data, int repeats = 0, bool cache = false) = 0;
virtual int playSoundFromSet(std::vector<soundBase::soundID> & sound_vec) = 0;
virtual void stopSound(int handler) = 0;
virtual ui32 getVolume() const = 0;
virtual void setVolume(ui32 percent) = 0;
virtual uint32_t getSoundDurationMilliseconds(const AudioPath & sound) = 0;
virtual void setCallback(int channel, std::function<void()> function) = 0;
virtual void resetCallback(int channel) = 0;
virtual void soundFinishedCallback(int channel) = 0;
virtual void ambientUpdateChannels(std::map<AudioPath, int> currentSounds) = 0;
virtual void ambientStopAllChannels() = 0;
virtual int ambientGetRange() const = 0;
};

View File

@@ -0,0 +1,43 @@
/*
* IVideoPlayer.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../lib/filesystem/ResourcePath.h"
struct SDL_Surface;
VCMI_LIB_NAMESPACE_BEGIN
class Point;
VCMI_LIB_NAMESPACE_END
enum class EVideoType : ui8
{
INTRO = 0, // use entire window: stopOnKey = true, scale = true, overlay = false
SPELLBOOK // overlay video: stopOnKey = false, scale = false, overlay = true
};
class IVideoPlayer : boost::noncopyable
{
public:
virtual bool open(const VideoPath & name, bool scale = false)=0; //true - succes
virtual void close()=0;
virtual bool nextFrame()=0;
virtual void show(int x, int y, SDL_Surface *dst, bool update = true)=0;
virtual void redraw(int x, int y, SDL_Surface *dst, bool update = true)=0; //reblits buffer
virtual bool wait()=0;
virtual int curFrame() const =0;
virtual int frameCount() const =0;
virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> restart = nullptr) = 0;
virtual bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType) = 0;
virtual std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) = 0;
virtual Point size() = 0;
virtual ~IVideoPlayer() = default;
};

View File

@@ -13,7 +13,6 @@
#include "Images.h"
#include "TextControls.h"
#include "../CMusicHandler.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../battle/BattleInterface.h"
@@ -23,6 +22,7 @@
#include "../gui/MouseButton.h"
#include "../gui/Shortcut.h"
#include "../gui/InterfaceObjectConfigurable.h"
#include "../media/ISoundPlayer.h"
#include "../windows/InfoWindows.h"
#include "../render/CAnimation.h"
#include "../render/Canvas.h"

View File

@@ -26,7 +26,6 @@
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../CMusicHandler.h"
#include "../../CCallback.h"

View File

@@ -18,12 +18,12 @@
#include "CCreatureWindow.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../gui/WindowHandler.h"
#include "../media/IMusicPlayer.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/CComponent.h"
#include "../widgets/CGarrisonInt.h"
@@ -43,6 +43,8 @@
#include "../../CCallback.h"
#include "../../lib/CArtHandler.h"
#include "../../lib/CBuildingHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CSoundBase.h"
#include "../../lib/CCreatureHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/GameSettings.h"

View File

@@ -11,13 +11,13 @@
#include "CPuzzleWindow.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../adventureMap/CResDataBar.h"
#include "../gui/CGuiHandler.h"
#include "../gui/TextAlignment.h"
#include "../gui/Shortcut.h"
#include "../mapView/MapView.h"
#include "../media/ISoundPlayer.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
#include "../widgets/TextControls.h"

View File

@@ -19,12 +19,12 @@
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../CVideoHandler.h"
#include "../battle/BattleInterface.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../gui/WindowHandler.h"
#include "../media/IVideoPlayer.h"
#include "../widgets/GraphicalPrimitiveCanvas.h"
#include "../widgets/CComponent.h"
#include "../widgets/CTextInput.h"

View File

@@ -16,11 +16,11 @@
#include "../../lib/CGeneralTextHandler.h"
#include "../CPlayerInterface.h"
#include "../CGameInfo.h"
#include "../CVideoHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../gui/WindowHandler.h"
#include "../media/IVideoPlayer.h"
#include "../widgets/Images.h"
#include "../widgets/Buttons.h"
#include "../widgets/TextControls.h"

View File

@@ -25,7 +25,6 @@
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../CMusicHandler.h"
#include "../../CCallback.h"

View File

@@ -19,15 +19,15 @@
#include "../CGameInfo.h"
#include "../CServerHandler.h"
#include "../Client.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../CVideoHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CursorHandler.h"
#include "../gui/Shortcut.h"
#include "../gui/WindowHandler.h"
#include "../media/IVideoPlayer.h"
#include "../widgets/CComponent.h"
#include "../widgets/CGarrisonInt.h"
#include "../widgets/CreatureCostBox.h"
@@ -58,6 +58,7 @@
#include "../lib/GameSettings.h"
#include "../lib/CondSh.h"
#include "../lib/CSkillHandler.h"
#include "../lib/CSoundBase.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/TextOperations.h"

View File

@@ -11,9 +11,10 @@
#include "GeneralOptionsTab.h"
#include "CGameInfo.h"
#include "CMusicHandler.h"
#include "CPlayerInterface.h"
#include "CServerHandler.h"
#include "media/IMusicPlayer.h"
#include "media/ISoundPlayer.h"
#include "render/IScreenHandler.h"
#include "windows/GUIClasses.h"