Browse Source

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
Ivan Savenko 1 year ago
parent
commit
d27b854cb1
51 changed files with 1284 additions and 1122 deletions
  1. 6 6
      client/CGameInfo.h
  2. 4 7
      client/CMT.cpp
  3. 14 4
      client/CMakeLists.txt
  4. 0 753
      client/CMusicHandler.cpp
  5. 0 170
      client/CMusicHandler.h
  6. 3 1
      client/CPlayerInterface.cpp
  7. 0 131
      client/CVideoHandler.h
  8. 2 1
      client/HeroMovementController.cpp
  9. 1 1
      client/adventureMap/CInGameConsole.cpp
  10. 1 1
      client/adventureMap/CInfoBar.cpp
  11. 2 1
      client/adventureMap/MapAudioPlayer.cpp
  12. 1 1
      client/adventureMap/TurnTimerWidget.cpp
  13. 1 1
      client/battle/BattleAnimationClasses.cpp
  14. 1 1
      client/battle/BattleEffectsController.cpp
  15. 10 2
      client/battle/BattleInterface.cpp
  16. 2 2
      client/battle/BattleInterfaceClasses.cpp
  17. 1 1
      client/battle/BattleObstacleController.cpp
  18. 1 1
      client/battle/BattleSiegeController.cpp
  19. 1 1
      client/battle/BattleStacksController.cpp
  20. 0 1
      client/battle/BattleWindow.cpp
  21. 2 1
      client/eventsSDL/InputHandler.cpp
  22. 1 1
      client/globalLobby/GlobalLobbyClient.cpp
  23. 1 1
      client/globalLobby/GlobalLobbyWidget.cpp
  24. 3 4
      client/lobby/CBonusSelection.cpp
  25. 1 2
      client/lobby/CSelectionBase.cpp
  26. 2 1
      client/lobby/OptionsTab.cpp
  27. 3 3
      client/mainmenu/CCampaignScreen.cpp
  28. 3 2
      client/mainmenu/CHighScoreScreen.cpp
  29. 2 2
      client/mainmenu/CMainMenu.cpp
  30. 3 2
      client/mainmenu/CPrologEpilogVideo.cpp
  31. 45 0
      client/media/CAudioBase.cpp
  32. 21 0
      client/media/CAudioBase.h
  33. 30 0
      client/media/CEmptyVideoPlayer.h
  34. 346 0
      client/media/CMusicHandler.cpp
  35. 94 0
      client/media/CMusicHandler.h
  36. 383 0
      client/media/CSoundHandler.cpp
  37. 78 0
      client/media/CSoundHandler.h
  38. 6 6
      client/media/CVideoHandler.cpp
  39. 86 0
      client/media/CVideoHandler.h
  40. 33 0
      client/media/IMusicPlayer.h
  41. 35 0
      client/media/ISoundPlayer.h
  42. 43 0
      client/media/IVideoPlayer.h
  43. 1 1
      client/widgets/Buttons.cpp
  44. 0 1
      client/widgets/Images.cpp
  45. 3 1
      client/windows/CCastleInterface.cpp
  46. 1 1
      client/windows/CPuzzleWindow.cpp
  47. 1 1
      client/windows/CSpellWindow.cpp
  48. 1 1
      client/windows/CTutorialWindow.cpp
  49. 0 1
      client/windows/CWindowObject.cpp
  50. 3 2
      client/windows/GUIClasses.cpp
  51. 2 1
      client/windows/settings/GeneralOptionsTab.cpp

+ 6 - 6
client/CGameInfo.h

@@ -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;
 

+ 4 - 7
client/CMT.cpp

@@ -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;

+ 14 - 4
client/CMakeLists.txt

@@ -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

+ 0 - 753
client/CMusicHandler.cpp

@@ -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;
-}

+ 0 - 170
client/CMusicHandler.h

@@ -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;
-};

+ 3 - 1
client/CPlayerInterface.cpp

@@ -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"

+ 0 - 131
client/CVideoHandler.h

@@ -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

+ 2 - 1
client/HeroMovementController.cpp

@@ -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"

+ 1 - 1
client/adventureMap/CInGameConsole.cpp

@@ -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"

+ 1 - 1
client/adventureMap/CInfoBar.cpp

@@ -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"

+ 2 - 1
client/adventureMap/MapAudioPlayer.cpp

@@ -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"

+ 1 - 1
client/adventureMap/TurnTimerWidget.cpp

@@ -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"

+ 1 - 1
client/battle/BattleAnimationClasses.cpp

@@ -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"

+ 1 - 1
client/battle/BattleEffectsController.cpp

@@ -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"

+ 10 - 2
client/battle/BattleInterface.cpp

@@ -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);

+ 2 - 2
client/battle/BattleInterfaceClasses.cpp

@@ -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"

+ 1 - 1
client/battle/BattleObstacleController.cpp

@@ -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"
 

+ 1 - 1
client/battle/BattleSiegeController.cpp

@@ -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"

+ 1 - 1
client/battle/BattleStacksController.cpp

@@ -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"

+ 0 - 1
client/battle/BattleWindow.cpp

@@ -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"

+ 2 - 1
client/eventsSDL/InputHandler.cpp

@@ -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"
 

+ 1 - 1
client/globalLobby/GlobalLobbyClient.cpp

@@ -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"

+ 1 - 1
client/globalLobby/GlobalLobbyWidget.cpp

@@ -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"

+ 3 - 4
client/lobby/CBonusSelection.cpp

@@ -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"

+ 1 - 2
client/lobby/CSelectionBase.cpp

@@ -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"

+ 2 - 1
client/lobby/OptionsTab.cpp

@@ -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"

+ 3 - 3
client/mainmenu/CCampaignScreen.cpp

@@ -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"

+ 3 - 2
client/mainmenu/CHighScoreScreen.cpp

@@ -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"

+ 2 - 2
client/mainmenu/CMainMenu.cpp

@@ -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"

+ 3 - 2
client/mainmenu/CPrologEpilogVideo.cpp

@@ -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"

+ 45 - 0
client/media/CAudioBase.cpp

@@ -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 - 0
client/media/CAudioBase.h

@@ -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();
+};

+ 30 - 0
client/media/CEmptyVideoPlayer.h

@@ -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); };
+};

+ 346 - 0
client/media/CMusicHandler.cpp

@@ -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;
+}

+ 94 - 0
client/media/CMusicHandler.h

@@ -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;
+};

+ 383 - 0
client/media/CSoundHandler.cpp

@@ -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();
+}

+ 78 - 0
client/media/CSoundHandler.h

@@ -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;
+};

+ 6 - 6
client/CVideoHandler.cpp → client/media/CVideoHandler.cpp

@@ -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.

+ 86 - 0
client/media/CVideoHandler.h

@@ -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

+ 33 - 0
client/media/IMusicPlayer.h

@@ -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;
+};

+ 35 - 0
client/media/ISoundPlayer.h

@@ -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;
+};

+ 43 - 0
client/media/IVideoPlayer.h

@@ -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;
+};

+ 1 - 1
client/widgets/Buttons.cpp

@@ -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"

+ 0 - 1
client/widgets/Images.cpp

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

+ 3 - 1
client/windows/CCastleInterface.cpp

@@ -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"

+ 1 - 1
client/windows/CPuzzleWindow.cpp

@@ -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"

+ 1 - 1
client/windows/CSpellWindow.cpp

@@ -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"

+ 1 - 1
client/windows/CTutorialWindow.cpp

@@ -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"

+ 0 - 1
client/windows/CWindowObject.cpp

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

+ 3 - 2
client/windows/GUIClasses.cpp

@@ -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"
 

+ 2 - 1
client/windows/settings/GeneralOptionsTab.cpp

@@ -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"