mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-23 22:37:55 +02:00
Merge pull request #3998 from IvanSavenko/refactor_sdl_media
[1.6] Reorganize SDL sound/music/video handling
This commit is contained in:
@@ -36,21 +36,21 @@ class CMap;
|
|||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|
||||||
class CMapHandler;
|
class CMapHandler;
|
||||||
class CSoundHandler;
|
class ISoundPlayer;
|
||||||
class CMusicHandler;
|
class IMusicPlayer;
|
||||||
class CursorHandler;
|
class CursorHandler;
|
||||||
class IMainVideoPlayer;
|
class IVideoPlayer;
|
||||||
class CServerHandler;
|
class CServerHandler;
|
||||||
|
|
||||||
//a class for non-mechanical client GUI classes
|
//a class for non-mechanical client GUI classes
|
||||||
class CClientState
|
class CClientState
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CSoundHandler * soundh;
|
ISoundPlayer * soundh;
|
||||||
CMusicHandler * musich;
|
IMusicPlayer * musich;
|
||||||
CConsoleHandler * consoleh;
|
CConsoleHandler * consoleh;
|
||||||
CursorHandler * curh;
|
CursorHandler * curh;
|
||||||
IMainVideoPlayer * videoh;
|
IVideoPlayer * videoh;
|
||||||
};
|
};
|
||||||
extern CClientState * CCS;
|
extern CClientState * CCS;
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,13 @@
|
|||||||
|
|
||||||
#include "CGameInfo.h"
|
#include "CGameInfo.h"
|
||||||
#include "mainmenu/CMainMenu.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 "gui/CursorHandler.h"
|
||||||
#include "eventsSDL/InputHandler.h"
|
#include "eventsSDL/InputHandler.h"
|
||||||
#include "CPlayerInterface.h"
|
#include "CPlayerInterface.h"
|
||||||
#include "CVideoHandler.h"
|
|
||||||
#include "CMusicHandler.h"
|
|
||||||
#include "gui/CGuiHandler.h"
|
#include "gui/CGuiHandler.h"
|
||||||
#include "gui/WindowHandler.h"
|
#include "gui/WindowHandler.h"
|
||||||
#include "CServerHandler.h"
|
#include "CServerHandler.h"
|
||||||
@@ -292,10 +294,8 @@ int main(int argc, char * argv[])
|
|||||||
{
|
{
|
||||||
//initializing audio
|
//initializing audio
|
||||||
CCS->soundh = new CSoundHandler();
|
CCS->soundh = new CSoundHandler();
|
||||||
CCS->soundh->init();
|
|
||||||
CCS->soundh->setVolume((ui32)settings["general"]["sound"].Float());
|
CCS->soundh->setVolume((ui32)settings["general"]["sound"].Float());
|
||||||
CCS->musich = new CMusicHandler();
|
CCS->musich = new CMusicHandler();
|
||||||
CCS->musich->init();
|
|
||||||
CCS->musich->setVolume((ui32)settings["general"]["music"].Float());
|
CCS->musich->setVolume((ui32)settings["general"]["music"].Float());
|
||||||
logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
|
logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
|
||||||
}
|
}
|
||||||
@@ -396,20 +396,13 @@ int main(int argc, char * argv[])
|
|||||||
//plays intro, ends when intro is over or button has been pressed (handles events)
|
//plays intro, ends when intro is over or button has been pressed (handles events)
|
||||||
void playIntro()
|
void playIntro()
|
||||||
{
|
{
|
||||||
auto audioData = CCS->videoh->getAudio(VideoPath::builtin("3DOLOGO.SMK"));
|
if(!CCS->videoh->playIntroVideo(VideoPath::builtin("3DOLOGO.SMK")))
|
||||||
int sound = CCS->soundh->playSound(audioData);
|
return;
|
||||||
if(CCS->videoh->openAndPlayVideo(VideoPath::builtin("3DOLOGO.SMK"), 0, 1, EVideoType::INTRO))
|
|
||||||
{
|
if (!CCS->videoh->playIntroVideo(VideoPath::builtin("NWCLOGO.SMK")))
|
||||||
audioData = CCS->videoh->getAudio(VideoPath::builtin("NWCLOGO.SMK"));
|
return;
|
||||||
sound = CCS->soundh->playSound(audioData);
|
|
||||||
if (CCS->videoh->openAndPlayVideo(VideoPath::builtin("NWCLOGO.SMK"), 0, 1, EVideoType::INTRO))
|
CCS->videoh->playIntroVideo(VideoPath::builtin("H3INTRO.SMK"));
|
||||||
{
|
|
||||||
audioData = CCS->videoh->getAudio(VideoPath::builtin("H3INTRO.SMK"));
|
|
||||||
sound = CCS->soundh->playSound(audioData);
|
|
||||||
CCS->videoh->openAndPlayVideo(VideoPath::builtin("H3INTRO.SMK"), 0, 1, EVideoType::INTRO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CCS->soundh->stopSound(sound);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mainLoop()
|
static void mainLoop()
|
||||||
@@ -457,9 +450,6 @@ static void mainLoop()
|
|||||||
// cleanup, mostly to remove false leaks from analyzer
|
// cleanup, mostly to remove false leaks from analyzer
|
||||||
if(CCS)
|
if(CCS)
|
||||||
{
|
{
|
||||||
CCS->musich->release();
|
|
||||||
CCS->soundh->release();
|
|
||||||
|
|
||||||
delete CCS->consoleh;
|
delete CCS->consoleh;
|
||||||
delete CCS->curh;
|
delete CCS->curh;
|
||||||
delete CCS->videoh;
|
delete CCS->videoh;
|
||||||
|
|||||||
@@ -75,6 +75,11 @@ set(client_SRCS
|
|||||||
mapView/MapViewModel.cpp
|
mapView/MapViewModel.cpp
|
||||||
mapView/mapHandler.cpp
|
mapView/mapHandler.cpp
|
||||||
|
|
||||||
|
media/CAudioBase.cpp
|
||||||
|
media/CMusicHandler.cpp
|
||||||
|
media/CSoundHandler.cpp
|
||||||
|
media/CVideoHandler.cpp
|
||||||
|
|
||||||
render/CAnimation.cpp
|
render/CAnimation.cpp
|
||||||
render/CBitmapHandler.cpp
|
render/CBitmapHandler.cpp
|
||||||
render/CDefFile.cpp
|
render/CDefFile.cpp
|
||||||
@@ -126,6 +131,7 @@ set(client_SRCS
|
|||||||
widgets/CArtifactsOfHeroMarket.cpp
|
widgets/CArtifactsOfHeroMarket.cpp
|
||||||
widgets/CArtifactsOfHeroBackpack.cpp
|
widgets/CArtifactsOfHeroBackpack.cpp
|
||||||
widgets/RadialMenu.cpp
|
widgets/RadialMenu.cpp
|
||||||
|
widgets/VideoWidget.cpp
|
||||||
widgets/markets/CAltarArtifacts.cpp
|
widgets/markets/CAltarArtifacts.cpp
|
||||||
widgets/markets/CAltarCreatures.cpp
|
widgets/markets/CAltarCreatures.cpp
|
||||||
widgets/markets/CArtifactsBuying.cpp
|
widgets/markets/CArtifactsBuying.cpp
|
||||||
@@ -163,11 +169,9 @@ set(client_SRCS
|
|||||||
|
|
||||||
CGameInfo.cpp
|
CGameInfo.cpp
|
||||||
CMT.cpp
|
CMT.cpp
|
||||||
CMusicHandler.cpp
|
|
||||||
CPlayerInterface.cpp
|
CPlayerInterface.cpp
|
||||||
PlayerLocalState.cpp
|
PlayerLocalState.cpp
|
||||||
CServerHandler.cpp
|
CServerHandler.cpp
|
||||||
CVideoHandler.cpp
|
|
||||||
Client.cpp
|
Client.cpp
|
||||||
ClientCommandManager.cpp
|
ClientCommandManager.cpp
|
||||||
GameChatHandler.cpp
|
GameChatHandler.cpp
|
||||||
@@ -260,6 +264,15 @@ set(client_HEADERS
|
|||||||
mapView/MapViewModel.h
|
mapView/MapViewModel.h
|
||||||
mapView/mapHandler.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/CAnimation.h
|
||||||
render/CBitmapHandler.h
|
render/CBitmapHandler.h
|
||||||
render/CDefFile.h
|
render/CDefFile.h
|
||||||
@@ -320,6 +333,7 @@ set(client_HEADERS
|
|||||||
widgets/CArtifactsOfHeroMarket.h
|
widgets/CArtifactsOfHeroMarket.h
|
||||||
widgets/CArtifactsOfHeroBackpack.h
|
widgets/CArtifactsOfHeroBackpack.h
|
||||||
widgets/RadialMenu.h
|
widgets/RadialMenu.h
|
||||||
|
widgets/VideoWidget.h
|
||||||
widgets/markets/CAltarArtifacts.h
|
widgets/markets/CAltarArtifacts.h
|
||||||
widgets/markets/CAltarCreatures.h
|
widgets/markets/CAltarCreatures.h
|
||||||
widgets/markets/CArtifactsBuying.h
|
widgets/markets/CArtifactsBuying.h
|
||||||
@@ -357,11 +371,9 @@ set(client_HEADERS
|
|||||||
|
|
||||||
CGameInfo.h
|
CGameInfo.h
|
||||||
CMT.h
|
CMT.h
|
||||||
CMusicHandler.h
|
|
||||||
CPlayerInterface.h
|
CPlayerInterface.h
|
||||||
PlayerLocalState.h
|
PlayerLocalState.h
|
||||||
CServerHandler.h
|
CServerHandler.h
|
||||||
CVideoHandler.h
|
|
||||||
Client.h
|
Client.h
|
||||||
ClientCommandManager.h
|
ClientCommandManager.h
|
||||||
ClientNetPackVisitors.h
|
ClientNetPackVisitors.h
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
#include "CGameInfo.h"
|
#include "CGameInfo.h"
|
||||||
#include "CMT.h"
|
#include "CMT.h"
|
||||||
#include "CMusicHandler.h"
|
|
||||||
#include "CServerHandler.h"
|
#include "CServerHandler.h"
|
||||||
#include "HeroMovementController.h"
|
#include "HeroMovementController.h"
|
||||||
#include "PlayerLocalState.h"
|
#include "PlayerLocalState.h"
|
||||||
@@ -41,6 +40,9 @@
|
|||||||
|
|
||||||
#include "mapView/mapHandler.h"
|
#include "mapView/mapHandler.h"
|
||||||
|
|
||||||
|
#include "media/IMusicPlayer.h"
|
||||||
|
#include "media/ISoundPlayer.h"
|
||||||
|
|
||||||
#include "render/CAnimation.h"
|
#include "render/CAnimation.h"
|
||||||
#include "render/IImage.h"
|
#include "render/IImage.h"
|
||||||
#include "render/IRenderHandler.h"
|
#include "render/IRenderHandler.h"
|
||||||
|
|||||||
@@ -1,713 +0,0 @@
|
|||||||
/*
|
|
||||||
* CVideoHandler.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 "CVideoHandler.h"
|
|
||||||
|
|
||||||
#include "CMT.h"
|
|
||||||
#include "gui/CGuiHandler.h"
|
|
||||||
#include "eventsSDL/InputHandler.h"
|
|
||||||
#include "gui/FramerateManager.h"
|
|
||||||
#include "renderSDL/SDL_Extensions.h"
|
|
||||||
#include "CPlayerInterface.h"
|
|
||||||
#include "../lib/filesystem/Filesystem.h"
|
|
||||||
#include "../lib/filesystem/CInputStream.h"
|
|
||||||
|
|
||||||
#include <SDL_render.h>
|
|
||||||
|
|
||||||
#ifndef DISABLE_VIDEO
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
#include <libavcodec/avcodec.h>
|
|
||||||
#include <libavutil/imgutils.h>
|
|
||||||
#include <libswscale/swscale.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
#pragma comment(lib, "avcodec.lib")
|
|
||||||
#pragma comment(lib, "avutil.lib")
|
|
||||||
#pragma comment(lib, "avformat.lib")
|
|
||||||
#pragma comment(lib, "swscale.lib")
|
|
||||||
#endif // _MSC_VER
|
|
||||||
|
|
||||||
// Define a set of functions to read data
|
|
||||||
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 bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
static si64 lodSeek(void * opaque, si64 pos, int whence)
|
|
||||||
{
|
|
||||||
auto video = reinterpret_cast<CVideoPlayer *>(opaque);
|
|
||||||
|
|
||||||
if (whence & AVSEEK_SIZE)
|
|
||||||
return video->data->getSize();
|
|
||||||
|
|
||||||
return video->data->seek(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define a set of functions to read data
|
|
||||||
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 bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
static si64 lodSeekAudio(void * opaque, si64 pos, int whence)
|
|
||||||
{
|
|
||||||
auto video = reinterpret_cast<CVideoPlayer *>(opaque);
|
|
||||||
|
|
||||||
if (whence & AVSEEK_SIZE)
|
|
||||||
return video->dataAudio->getSize();
|
|
||||||
|
|
||||||
return video->dataAudio->seek(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
CVideoPlayer::CVideoPlayer()
|
|
||||||
: stream(-1)
|
|
||||||
, format (nullptr)
|
|
||||||
, codecContext(nullptr)
|
|
||||||
, codec(nullptr)
|
|
||||||
, frame(nullptr)
|
|
||||||
, sws(nullptr)
|
|
||||||
, context(nullptr)
|
|
||||||
, texture(nullptr)
|
|
||||||
, dest(nullptr)
|
|
||||||
, destRect(0,0,0,0)
|
|
||||||
, pos(0,0,0,0)
|
|
||||||
, frameTime(0)
|
|
||||||
, doLoop(false)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool CVideoPlayer::open(const VideoPath & fname, bool scale)
|
|
||||||
{
|
|
||||||
return open(fname, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop = to loop through the video
|
|
||||||
// overlay = directly write to the screen.
|
|
||||||
bool CVideoPlayer::open(const VideoPath & videoToOpen, bool loop, bool overlay, bool scale)
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
|
|
||||||
doLoop = loop;
|
|
||||||
frameTime = 0;
|
|
||||||
|
|
||||||
if (CResourceHandler::get()->existsResource(videoToOpen))
|
|
||||||
fname = videoToOpen;
|
|
||||||
else
|
|
||||||
fname = videoToOpen.addPrefix("VIDEO/");
|
|
||||||
|
|
||||||
if (!CResourceHandler::get()->existsResource(fname))
|
|
||||||
{
|
|
||||||
logGlobal->error("Error: video %s was not found", fname.getName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = CResourceHandler::get()->load(fname);
|
|
||||||
|
|
||||||
static const int BUFFER_SIZE = 4096;
|
|
||||||
|
|
||||||
unsigned char * buffer = (unsigned char *)av_malloc(BUFFER_SIZE);// will be freed by ffmpeg
|
|
||||||
context = avio_alloc_context( buffer, BUFFER_SIZE, 0, (void *)this, lodRead, nullptr, lodSeek);
|
|
||||||
|
|
||||||
format = avformat_alloc_context();
|
|
||||||
format->pb = context;
|
|
||||||
// filename is not needed - file was already open and stored in this->data;
|
|
||||||
int avfopen = avformat_open_input(&format, "dummyFilename", nullptr, nullptr);
|
|
||||||
|
|
||||||
if (avfopen != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Retrieve stream information
|
|
||||||
if (avformat_find_stream_info(format, nullptr) < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Find the first video stream
|
|
||||||
stream = -1;
|
|
||||||
for(ui32 i=0; i<format->nb_streams; i++)
|
|
||||||
{
|
|
||||||
if (format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
|
|
||||||
{
|
|
||||||
stream = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream < 0)
|
|
||||||
// No video stream in that file
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Find the decoder for the video stream
|
|
||||||
codec = avcodec_find_decoder(format->streams[stream]->codecpar->codec_id);
|
|
||||||
|
|
||||||
if (codec == nullptr)
|
|
||||||
{
|
|
||||||
// Unsupported codec
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
codecContext = avcodec_alloc_context3(codec);
|
|
||||||
if(!codecContext)
|
|
||||||
return false;
|
|
||||||
// Get a pointer to the codec context for the video stream
|
|
||||||
int ret = avcodec_parameters_to_context(codecContext, format->streams[stream]->codecpar);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
//We cannot get codec from parameters
|
|
||||||
avcodec_free_context(&codecContext);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open codec
|
|
||||||
if ( avcodec_open2(codecContext, codec, nullptr) < 0 )
|
|
||||||
{
|
|
||||||
// Could not open codec
|
|
||||||
codec = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Allocate video frame
|
|
||||||
frame = av_frame_alloc();
|
|
||||||
|
|
||||||
//setup scaling
|
|
||||||
if(scale)
|
|
||||||
{
|
|
||||||
pos.w = screen->w;
|
|
||||||
pos.h = screen->h;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pos.w = codecContext->width;
|
|
||||||
pos.h = codecContext->height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate a place to put our YUV image on that screen
|
|
||||||
if (overlay)
|
|
||||||
{
|
|
||||||
texture = SDL_CreateTexture( mainRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STATIC, pos.w, pos.h);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dest = CSDL_Ext::newSurface(pos.w, pos.h);
|
|
||||||
destRect.x = destRect.y = 0;
|
|
||||||
destRect.w = pos.w;
|
|
||||||
destRect.h = pos.h;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (texture == nullptr && dest == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (texture)
|
|
||||||
{ // Convert the image into YUV format that SDL uses
|
|
||||||
sws = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,
|
|
||||||
pos.w, pos.h,
|
|
||||||
AV_PIX_FMT_YUV420P,
|
|
||||||
SWS_BICUBIC, nullptr, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AVPixelFormat screenFormat = AV_PIX_FMT_NONE;
|
|
||||||
if (screen->format->Bshift > screen->format->Rshift)
|
|
||||||
{
|
|
||||||
// this a BGR surface
|
|
||||||
switch (screen->format->BytesPerPixel)
|
|
||||||
{
|
|
||||||
case 2: screenFormat = AV_PIX_FMT_BGR565; break;
|
|
||||||
case 3: screenFormat = AV_PIX_FMT_BGR24; break;
|
|
||||||
case 4: screenFormat = AV_PIX_FMT_BGR32; break;
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// this a RGB surface
|
|
||||||
switch (screen->format->BytesPerPixel)
|
|
||||||
{
|
|
||||||
case 2: screenFormat = AV_PIX_FMT_RGB565; break;
|
|
||||||
case 3: screenFormat = AV_PIX_FMT_RGB24; break;
|
|
||||||
case 4: screenFormat = AV_PIX_FMT_RGB32; break;
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sws = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,
|
|
||||||
pos.w, pos.h, screenFormat,
|
|
||||||
SWS_BICUBIC, nullptr, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sws == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the next frame. Return false on error/end of file.
|
|
||||||
bool CVideoPlayer::nextFrame()
|
|
||||||
{
|
|
||||||
AVPacket packet;
|
|
||||||
int frameFinished = 0;
|
|
||||||
bool gotError = false;
|
|
||||||
|
|
||||||
if (sws == nullptr)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
while(!frameFinished)
|
|
||||||
{
|
|
||||||
int ret = av_read_frame(format, &packet);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
// Error. It's probably an end of file.
|
|
||||||
if (doLoop && !gotError)
|
|
||||||
{
|
|
||||||
// Rewind
|
|
||||||
frameTime = 0;
|
|
||||||
if (av_seek_frame(format, stream, 0, AVSEEK_FLAG_BYTE) < 0)
|
|
||||||
break;
|
|
||||||
gotError = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Is this a packet from the video stream?
|
|
||||||
if (packet.stream_index == stream)
|
|
||||||
{
|
|
||||||
// Decode video frame
|
|
||||||
int rc = avcodec_send_packet(codecContext, &packet);
|
|
||||||
if (rc >=0)
|
|
||||||
packet.size = 0;
|
|
||||||
rc = avcodec_receive_frame(codecContext, frame);
|
|
||||||
if (rc >= 0)
|
|
||||||
frameFinished = 1;
|
|
||||||
// Did we get a video frame?
|
|
||||||
if (frameFinished)
|
|
||||||
{
|
|
||||||
uint8_t *data[4];
|
|
||||||
int linesize[4];
|
|
||||||
|
|
||||||
if (texture) {
|
|
||||||
av_image_alloc(data, linesize, pos.w, pos.h, AV_PIX_FMT_YUV420P, 1);
|
|
||||||
|
|
||||||
sws_scale(sws, frame->data, frame->linesize,
|
|
||||||
0, codecContext->height, data, linesize);
|
|
||||||
|
|
||||||
SDL_UpdateYUVTexture(texture, nullptr, data[0], linesize[0],
|
|
||||||
data[1], linesize[1],
|
|
||||||
data[2], linesize[2]);
|
|
||||||
av_freep(&data[0]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Avoid buffer overflow caused by sws_scale():
|
|
||||||
* http://trac.ffmpeg.org/ticket/9254
|
|
||||||
* Currently (ffmpeg-4.4 with SSE3 enabled) sws_scale()
|
|
||||||
* has a few requirements for target data buffers on rescaling:
|
|
||||||
* 1. buffer has to be aligned to be usable for SIMD instructions
|
|
||||||
* 2. buffer has to be padded to allow small overflow by SIMD instructions
|
|
||||||
* Unfortunately SDL_Surface does not provide these guarantees.
|
|
||||||
* This means that atempt to rescale directly into SDL surface causes
|
|
||||||
* memory corruption. Usually it happens on campaign selection screen
|
|
||||||
* where short video moves start spinning on mouse hover.
|
|
||||||
*
|
|
||||||
* To fix [1.] we use av_malloc() for memory allocation.
|
|
||||||
* To fix [2.] we add an `ffmpeg_pad` that provides plenty of space.
|
|
||||||
* We have to use intermdiate buffer and then use memcpy() to land it
|
|
||||||
* to SDL_Surface.
|
|
||||||
*/
|
|
||||||
size_t pic_bytes = dest->pitch * dest->h;
|
|
||||||
size_t ffmped_pad = 1024; /* a few bytes of overflow will go here */
|
|
||||||
void * for_sws = av_malloc (pic_bytes + ffmped_pad);
|
|
||||||
data[0] = (ui8 *)for_sws;
|
|
||||||
linesize[0] = dest->pitch;
|
|
||||||
|
|
||||||
sws_scale(sws, frame->data, frame->linesize,
|
|
||||||
0, codecContext->height, data, linesize);
|
|
||||||
memcpy(dest->pixels, for_sws, pic_bytes);
|
|
||||||
av_free(for_sws);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
av_packet_unref(&packet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return frameFinished != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CVideoPlayer::show( int x, int y, SDL_Surface *dst, bool update )
|
|
||||||
{
|
|
||||||
if (sws == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
pos.x = x;
|
|
||||||
pos.y = y;
|
|
||||||
CSDL_Ext::blitSurface(dest, destRect, dst, pos.topLeft());
|
|
||||||
|
|
||||||
if (update)
|
|
||||||
CSDL_Ext::updateRect(dst, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CVideoPlayer::redraw( int x, int y, SDL_Surface *dst, bool update )
|
|
||||||
{
|
|
||||||
show(x, y, dst, update);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, bool update, std::function<void()> onVideoRestart)
|
|
||||||
{
|
|
||||||
if (sws == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
#if (LIBAVUTIL_VERSION_MAJOR < 58)
|
|
||||||
auto packet_duration = frame->pkt_duration;
|
|
||||||
#else
|
|
||||||
auto packet_duration = frame->duration;
|
|
||||||
#endif
|
|
||||||
double frameEndTime = (frame->pts + packet_duration) * av_q2d(format->streams[stream]->time_base);
|
|
||||||
frameTime += GH.framerate().getElapsedMilliseconds() / 1000.0;
|
|
||||||
|
|
||||||
if (frameTime >= frameEndTime )
|
|
||||||
{
|
|
||||||
if (nextFrame())
|
|
||||||
show(x,y,dst,update);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(onVideoRestart)
|
|
||||||
onVideoRestart();
|
|
||||||
VideoPath filenameToReopen = fname; // create copy to backup this->fname
|
|
||||||
open(filenameToReopen);
|
|
||||||
nextFrame();
|
|
||||||
|
|
||||||
// The y position is wrong at the first frame.
|
|
||||||
// Note: either the windows player or the linux player is
|
|
||||||
// broken. Compensate here until the bug is found.
|
|
||||||
show(x, y--, dst, update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
redraw(x, y, dst, update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CVideoPlayer::close()
|
|
||||||
{
|
|
||||||
fname = VideoPath();
|
|
||||||
|
|
||||||
if (sws)
|
|
||||||
{
|
|
||||||
sws_freeContext(sws);
|
|
||||||
sws = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (texture)
|
|
||||||
{
|
|
||||||
SDL_DestroyTexture(texture);
|
|
||||||
texture = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dest)
|
|
||||||
{
|
|
||||||
SDL_FreeSurface(dest);
|
|
||||||
dest = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame)
|
|
||||||
{
|
|
||||||
av_frame_free(&frame);//will be set to null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (codec)
|
|
||||||
{
|
|
||||||
avcodec_close(codecContext);
|
|
||||||
codec = nullptr;
|
|
||||||
}
|
|
||||||
if (codecContext)
|
|
||||||
{
|
|
||||||
avcodec_free_context(&codecContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format)
|
|
||||||
{
|
|
||||||
avformat_close_input(&format);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context)
|
|
||||||
{
|
|
||||||
av_free(context);
|
|
||||||
context = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<std::unique_ptr<ui8 []>, si64> CVideoPlayer::getAudio(const VideoPath & videoToOpen)
|
|
||||||
{
|
|
||||||
std::pair<std::unique_ptr<ui8 []>, si64> dat(std::make_pair(nullptr, 0));
|
|
||||||
|
|
||||||
VideoPath fnameAudio;
|
|
||||||
|
|
||||||
if (CResourceHandler::get()->existsResource(videoToOpen))
|
|
||||||
fnameAudio = videoToOpen;
|
|
||||||
else
|
|
||||||
fnameAudio = videoToOpen.addPrefix("VIDEO/");
|
|
||||||
|
|
||||||
if (!CResourceHandler::get()->existsResource(fnameAudio))
|
|
||||||
{
|
|
||||||
logGlobal->error("Error: video %s was not found", fnameAudio.getName());
|
|
||||||
return dat;
|
|
||||||
}
|
|
||||||
|
|
||||||
dataAudio = CResourceHandler::get()->load(fnameAudio);
|
|
||||||
|
|
||||||
static const int BUFFER_SIZE = 4096;
|
|
||||||
|
|
||||||
unsigned char * bufferAudio = (unsigned char *)av_malloc(BUFFER_SIZE);// will be freed by ffmpeg
|
|
||||||
AVIOContext * contextAudio = avio_alloc_context( bufferAudio, BUFFER_SIZE, 0, (void *)this, lodReadAudio, nullptr, lodSeekAudio);
|
|
||||||
|
|
||||||
AVFormatContext * formatAudio = avformat_alloc_context();
|
|
||||||
formatAudio->pb = contextAudio;
|
|
||||||
// filename is not needed - file was already open and stored in this->data;
|
|
||||||
int avfopen = avformat_open_input(&formatAudio, "dummyFilename", nullptr, nullptr);
|
|
||||||
|
|
||||||
if (avfopen != 0)
|
|
||||||
{
|
|
||||||
return dat;
|
|
||||||
}
|
|
||||||
// Retrieve stream information
|
|
||||||
if (avformat_find_stream_info(formatAudio, nullptr) < 0)
|
|
||||||
return dat;
|
|
||||||
|
|
||||||
// Find the first audio stream
|
|
||||||
int streamAudio = -1;
|
|
||||||
for(ui32 i = 0; i < formatAudio->nb_streams; i++)
|
|
||||||
{
|
|
||||||
if (formatAudio->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
|
||||||
{
|
|
||||||
streamAudio = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(streamAudio < 0)
|
|
||||||
return dat;
|
|
||||||
|
|
||||||
const AVCodec *codecAudio = avcodec_find_decoder(formatAudio->streams[streamAudio]->codecpar->codec_id);
|
|
||||||
|
|
||||||
AVCodecContext *codecContextAudio;
|
|
||||||
if (codecAudio != nullptr)
|
|
||||||
codecContextAudio = avcodec_alloc_context3(codecAudio);
|
|
||||||
|
|
||||||
// Get a pointer to the codec context for the audio stream
|
|
||||||
if (streamAudio > -1)
|
|
||||||
{
|
|
||||||
int ret = avcodec_parameters_to_context(codecContextAudio, formatAudio->streams[streamAudio]->codecpar);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
//We cannot get codec from parameters
|
|
||||||
avcodec_free_context(&codecContextAudio);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open codec
|
|
||||||
AVFrame *frameAudio;
|
|
||||||
if (codecAudio != nullptr)
|
|
||||||
{
|
|
||||||
if ( avcodec_open2(codecContextAudio, codecAudio, nullptr) < 0 )
|
|
||||||
{
|
|
||||||
// Could not open codec
|
|
||||||
codecAudio = nullptr;
|
|
||||||
}
|
|
||||||
// Allocate audio frame
|
|
||||||
frameAudio = av_frame_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
AVPacket packet;
|
|
||||||
|
|
||||||
std::vector<ui8> samples;
|
|
||||||
|
|
||||||
while (av_read_frame(formatAudio, &packet) >= 0)
|
|
||||||
{
|
|
||||||
if(packet.stream_index == streamAudio)
|
|
||||||
{
|
|
||||||
int rc = avcodec_send_packet(codecContextAudio, &packet);
|
|
||||||
if (rc >= 0)
|
|
||||||
packet.size = 0;
|
|
||||||
rc = avcodec_receive_frame(codecContextAudio, frameAudio);
|
|
||||||
int bytesToRead = (frameAudio->nb_samples * 2 * (formatAudio->streams[streamAudio]->codecpar->bits_per_coded_sample / 8));
|
|
||||||
if (rc >= 0)
|
|
||||||
for (int s = 0; s < bytesToRead; s += sizeof(ui8))
|
|
||||||
{
|
|
||||||
ui8 value;
|
|
||||||
memcpy(&value, &frameAudio->data[0][s], sizeof(ui8));
|
|
||||||
samples.push_back(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
av_packet_unref(&packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct WAV_HEADER {
|
|
||||||
ui8 RIFF[4] = {'R', 'I', 'F', 'F'};
|
|
||||||
ui32 ChunkSize;
|
|
||||||
ui8 WAVE[4] = {'W', 'A', 'V', 'E'};
|
|
||||||
ui8 fmt[4] = {'f', 'm', 't', ' '};
|
|
||||||
ui32 Subchunk1Size = 16;
|
|
||||||
ui16 AudioFormat = 1;
|
|
||||||
ui16 NumOfChan = 2;
|
|
||||||
ui32 SamplesPerSec = 22050;
|
|
||||||
ui32 bytesPerSec = 22050 * 2;
|
|
||||||
ui16 blockAlign = 2;
|
|
||||||
ui16 bitsPerSample = 16;
|
|
||||||
ui8 Subchunk2ID[4] = {'d', 'a', 't', 'a'};
|
|
||||||
ui32 Subchunk2Size;
|
|
||||||
} wav_hdr;
|
|
||||||
|
|
||||||
wav_hdr wav;
|
|
||||||
wav.ChunkSize = samples.size() + sizeof(wav_hdr) - 8;
|
|
||||||
wav.Subchunk2Size = samples.size() + sizeof(wav_hdr) - 44;
|
|
||||||
wav.SamplesPerSec = formatAudio->streams[streamAudio]->codecpar->sample_rate;
|
|
||||||
wav.bitsPerSample = formatAudio->streams[streamAudio]->codecpar->bits_per_coded_sample;
|
|
||||||
auto wavPtr = reinterpret_cast<ui8*>(&wav);
|
|
||||||
|
|
||||||
dat = std::make_pair(std::make_unique<ui8[]>(samples.size() + sizeof(wav_hdr)), samples.size() + sizeof(wav_hdr));
|
|
||||||
std::copy(wavPtr, wavPtr + sizeof(wav_hdr), dat.first.get());
|
|
||||||
std::copy(samples.begin(), samples.end(), dat.first.get() + sizeof(wav_hdr));
|
|
||||||
|
|
||||||
if (frameAudio)
|
|
||||||
av_frame_free(&frameAudio);
|
|
||||||
|
|
||||||
if (codecAudio)
|
|
||||||
{
|
|
||||||
avcodec_close(codecContextAudio);
|
|
||||||
codecAudio = nullptr;
|
|
||||||
}
|
|
||||||
if (codecContextAudio)
|
|
||||||
avcodec_free_context(&codecContextAudio);
|
|
||||||
|
|
||||||
if (formatAudio)
|
|
||||||
avformat_close_input(&formatAudio);
|
|
||||||
|
|
||||||
if (contextAudio)
|
|
||||||
{
|
|
||||||
av_free(contextAudio);
|
|
||||||
contextAudio = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dat;
|
|
||||||
}
|
|
||||||
|
|
||||||
Point CVideoPlayer::size()
|
|
||||||
{
|
|
||||||
if(frame)
|
|
||||||
return Point(frame->width, frame->height);
|
|
||||||
else
|
|
||||||
return Point(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Plays a video. Only works for overlays.
|
|
||||||
bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey, bool overlay)
|
|
||||||
{
|
|
||||||
// Note: either the windows player or the linux player is
|
|
||||||
// broken. Compensate here until the bug is found.
|
|
||||||
y--;
|
|
||||||
|
|
||||||
pos.x = x;
|
|
||||||
pos.y = y;
|
|
||||||
frameTime = 0.0;
|
|
||||||
|
|
||||||
auto lastTimePoint = boost::chrono::steady_clock::now();
|
|
||||||
|
|
||||||
while(nextFrame())
|
|
||||||
{
|
|
||||||
if(stopOnKey)
|
|
||||||
{
|
|
||||||
GH.input().fetchEvents();
|
|
||||||
if(GH.input().ignoreEventsUntilInput())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Rect rect = CSDL_Ext::toSDL(pos);
|
|
||||||
|
|
||||||
if(overlay)
|
|
||||||
{
|
|
||||||
SDL_RenderFillRect(mainRenderer, &rect);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SDL_RenderClear(mainRenderer);
|
|
||||||
}
|
|
||||||
SDL_RenderCopy(mainRenderer, texture, nullptr, &rect);
|
|
||||||
SDL_RenderPresent(mainRenderer);
|
|
||||||
|
|
||||||
#if (LIBAVUTIL_VERSION_MAJOR < 58)
|
|
||||||
auto packet_duration = frame->pkt_duration;
|
|
||||||
#else
|
|
||||||
auto packet_duration = frame->duration;
|
|
||||||
#endif
|
|
||||||
// Framerate delay
|
|
||||||
double targetFrameTimeSeconds = packet_duration * av_q2d(format->streams[stream]->time_base);
|
|
||||||
auto targetFrameTime = boost::chrono::milliseconds(static_cast<int>(1000 * (targetFrameTimeSeconds)));
|
|
||||||
|
|
||||||
auto timePointAfterPresent = boost::chrono::steady_clock::now();
|
|
||||||
auto timeSpentBusy = boost::chrono::duration_cast<boost::chrono::milliseconds>(timePointAfterPresent - lastTimePoint);
|
|
||||||
|
|
||||||
if (targetFrameTime > timeSpentBusy)
|
|
||||||
boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy);
|
|
||||||
|
|
||||||
lastTimePoint = boost::chrono::steady_clock::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CVideoPlayer::openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType)
|
|
||||||
{
|
|
||||||
bool scale;
|
|
||||||
bool stopOnKey;
|
|
||||||
bool overlay;
|
|
||||||
|
|
||||||
switch(videoType)
|
|
||||||
{
|
|
||||||
case EVideoType::INTRO:
|
|
||||||
stopOnKey = true;
|
|
||||||
scale = true;
|
|
||||||
overlay = false;
|
|
||||||
break;
|
|
||||||
case EVideoType::SPELLBOOK:
|
|
||||||
default:
|
|
||||||
stopOnKey = false;
|
|
||||||
scale = false;
|
|
||||||
overlay = true;
|
|
||||||
}
|
|
||||||
open(name, false, true, scale);
|
|
||||||
bool ret = playVideo(x, y, stopOnKey, overlay);
|
|
||||||
close();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
CVideoPlayer::~CVideoPlayer()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
#include "HeroMovementController.h"
|
#include "HeroMovementController.h"
|
||||||
|
|
||||||
#include "CGameInfo.h"
|
#include "CGameInfo.h"
|
||||||
#include "CMusicHandler.h"
|
|
||||||
#include "CPlayerInterface.h"
|
#include "CPlayerInterface.h"
|
||||||
#include "PlayerLocalState.h"
|
#include "PlayerLocalState.h"
|
||||||
#include "adventureMap/AdventureMapInterface.h"
|
#include "adventureMap/AdventureMapInterface.h"
|
||||||
@@ -19,10 +18,12 @@
|
|||||||
#include "gui/CGuiHandler.h"
|
#include "gui/CGuiHandler.h"
|
||||||
#include "gui/CursorHandler.h"
|
#include "gui/CursorHandler.h"
|
||||||
#include "mapView/mapHandler.h"
|
#include "mapView/mapHandler.h"
|
||||||
|
#include "media/ISoundPlayer.h"
|
||||||
|
|
||||||
#include "../CCallback.h"
|
#include "../CCallback.h"
|
||||||
|
|
||||||
#include "../lib/CondSh.h"
|
#include "../lib/CondSh.h"
|
||||||
|
#include "../lib/CConfigHandler.h"
|
||||||
#include "../lib/pathfinder/CGPathNode.h"
|
#include "../lib/pathfinder/CGPathNode.h"
|
||||||
#include "../lib/mapObjects/CGHeroInstance.h"
|
#include "../lib/mapObjects/CGHeroInstance.h"
|
||||||
#include "../lib/networkPacks/PacksForClient.h"
|
#include "../lib/networkPacks/PacksForClient.h"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include "CInGameConsole.h"
|
#include "CInGameConsole.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../CServerHandler.h"
|
#include "../CServerHandler.h"
|
||||||
#include "../GameChatHandler.h"
|
#include "../GameChatHandler.h"
|
||||||
@@ -21,6 +20,7 @@
|
|||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
#include "../gui/TextAlignment.h"
|
#include "../gui/TextAlignment.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../render/Colors.h"
|
#include "../render/Colors.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
#include "../render/IScreenHandler.h"
|
#include "../render/IScreenHandler.h"
|
||||||
|
|||||||
@@ -20,11 +20,11 @@
|
|||||||
#include "../widgets/MiscWidgets.h"
|
#include "../widgets/MiscWidgets.h"
|
||||||
#include "../windows/InfoWindows.h"
|
#include "../windows/InfoWindows.h"
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../PlayerLocalState.h"
|
#include "../PlayerLocalState.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../render/IScreenHandler.h"
|
#include "../render/IScreenHandler.h"
|
||||||
|
|
||||||
#include "../../CCallback.h"
|
#include "../../CCallback.h"
|
||||||
|
|||||||
@@ -12,9 +12,10 @@
|
|||||||
|
|
||||||
#include "../CCallback.h"
|
#include "../CCallback.h"
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../mapView/mapHandler.h"
|
#include "../mapView/mapHandler.h"
|
||||||
|
#include "../media/IMusicPlayer.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
|
|
||||||
#include "../../lib/TerrainHandler.h"
|
#include "../../lib/TerrainHandler.h"
|
||||||
#include "../../lib/mapObjects/CArmedInstance.h"
|
#include "../../lib/mapObjects/CArmedInstance.h"
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
#include "TurnTimerWidget.h"
|
#include "TurnTimerWidget.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../battle/BattleInterface.h"
|
#include "../battle/BattleInterface.h"
|
||||||
#include "../battle/BattleStacksController.h"
|
#include "../battle/BattleStacksController.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../render/Graphics.h"
|
#include "../render/Graphics.h"
|
||||||
#include "../widgets/Images.h"
|
#include "../widgets/Images.h"
|
||||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||||
|
|||||||
@@ -20,10 +20,10 @@
|
|||||||
#include "CreatureAnimation.h"
|
#include "CreatureAnimation.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../gui/CursorHandler.h"
|
#include "../gui/CursorHandler.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../render/IRenderHandler.h"
|
#include "../render/IRenderHandler.h"
|
||||||
|
|
||||||
#include "../../CCallback.h"
|
#include "../../CCallback.h"
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
#include "BattleStacksController.h"
|
#include "BattleStacksController.h"
|
||||||
#include "BattleRenderer.h"
|
#include "BattleRenderer.h"
|
||||||
|
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
#include "../render/CAnimation.h"
|
#include "../render/CAnimation.h"
|
||||||
#include "../render/Graphics.h"
|
#include "../render/Graphics.h"
|
||||||
|
|||||||
@@ -24,11 +24,12 @@
|
|||||||
#include "BattleRenderer.h"
|
#include "BattleRenderer.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../gui/CursorHandler.h"
|
#include "../gui/CursorHandler.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
|
#include "../media/IMusicPlayer.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../windows/CTutorialWindow.h"
|
#include "../windows/CTutorialWindow.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
#include "../adventureMap/AdventureMapInterface.h"
|
#include "../adventureMap/AdventureMapInterface.h"
|
||||||
@@ -113,7 +114,14 @@ void BattleInterface::playIntroSoundAndUnlockInterface()
|
|||||||
onIntroSoundPlayed();
|
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)
|
if (battleIntroSoundChannel != -1)
|
||||||
{
|
{
|
||||||
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
|
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
|
||||||
|
|||||||
@@ -19,14 +19,13 @@
|
|||||||
#include "BattleWindow.h"
|
#include "BattleWindow.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../CVideoHandler.h"
|
|
||||||
#include "../gui/CursorHandler.h"
|
#include "../gui/CursorHandler.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
#include "../gui/MouseButton.h"
|
#include "../gui/MouseButton.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
|
#include "../media/IMusicPlayer.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
#include "../render/IImage.h"
|
#include "../render/IImage.h"
|
||||||
#include "../render/IFont.h"
|
#include "../render/IFont.h"
|
||||||
@@ -35,6 +34,7 @@
|
|||||||
#include "../widgets/Images.h"
|
#include "../widgets/Images.h"
|
||||||
#include "../widgets/Slider.h"
|
#include "../widgets/Slider.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
|
#include "../widgets/VideoWidget.h"
|
||||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||||
#include "../windows/CMessage.h"
|
#include "../windows/CMessage.h"
|
||||||
#include "../windows/CCreatureWindow.h"
|
#include "../windows/CCreatureWindow.h"
|
||||||
@@ -603,7 +603,7 @@ HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)
|
|||||||
}
|
}
|
||||||
|
|
||||||
BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner, bool allowReplay)
|
BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface & _owner, bool allowReplay)
|
||||||
: owner(_owner), currentVideo(BattleResultVideo::NONE)
|
: owner(_owner)
|
||||||
{
|
{
|
||||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||||
|
|
||||||
@@ -705,68 +705,98 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto resources = getResources(br);
|
||||||
|
|
||||||
|
description = std::make_shared<CTextBox>(resources.resultText.toString(), Rect(69, 203, 330, 68), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
|
||||||
|
videoPlayer = std::make_shared<VideoWidget>(Point(107, 70), resources.prologueVideo, resources.loopedVideo, false);
|
||||||
|
|
||||||
|
CCS->musich->playMusic(resources.musicName, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
BattleResultResources BattleResultWindow::getResources(const BattleResult & br)
|
||||||
|
{
|
||||||
//printing result description
|
//printing result description
|
||||||
bool weAreAttacker = !(owner.cb->getBattle(br.battleID)->battleGetMySide());
|
bool weAreAttacker = !(owner.cb->getBattle(br.battleID)->battleGetMySide());
|
||||||
if((br.winner == 0 && weAreAttacker) || (br.winner == 1 && !weAreAttacker)) //we've won
|
bool weAreDefender = !weAreAttacker;
|
||||||
|
bool weWon = (br.winner == 0 && weAreAttacker) || (br.winner == 1 && !weAreAttacker);
|
||||||
|
bool isSiege = owner.cb->getBattle(br.battleID)->battleGetDefendedTown() != nullptr;
|
||||||
|
|
||||||
|
BattleResultResources resources;
|
||||||
|
|
||||||
|
if(weWon)
|
||||||
{
|
{
|
||||||
int text = 304;
|
if(isSiege && weAreDefender)
|
||||||
currentVideo = BattleResultVideo::WIN;
|
{
|
||||||
|
resources.musicName = AudioPath::builtin("Music/Defend Castle");
|
||||||
|
resources.prologueVideo = VideoPath::builtin("DEFENDALL.BIK");
|
||||||
|
resources.loopedVideo = VideoPath::builtin("defendloop.bik");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resources.musicName = AudioPath::builtin("Music/Win Battle");
|
||||||
|
resources.prologueVideo = VideoPath();
|
||||||
|
resources.loopedVideo = VideoPath::builtin("WIN3.BIK");
|
||||||
|
}
|
||||||
|
|
||||||
switch(br.result)
|
switch(br.result)
|
||||||
{
|
{
|
||||||
case EBattleResult::NORMAL:
|
case EBattleResult::NORMAL:
|
||||||
if(owner.cb->getBattle(br.battleID)->battleGetDefendedTown() && !weAreAttacker)
|
resources.resultText.appendTextID("core.genrltxt.304");
|
||||||
currentVideo = BattleResultVideo::WIN_SIEGE;
|
|
||||||
break;
|
break;
|
||||||
case EBattleResult::ESCAPE:
|
case EBattleResult::ESCAPE:
|
||||||
text = 303;
|
resources.resultText.appendTextID("core.genrltxt.303");
|
||||||
break;
|
break;
|
||||||
case EBattleResult::SURRENDER:
|
case EBattleResult::SURRENDER:
|
||||||
text = 302;
|
resources.resultText.appendTextID("core.genrltxt.302");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
logGlobal->error("Invalid battle result code %d. Assumed normal.", static_cast<int>(br.result));
|
throw std::runtime_error("Invalid battle result!");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
playVideo();
|
|
||||||
|
|
||||||
std::string str = CGI->generaltexth->allTexts[text];
|
|
||||||
|
|
||||||
const CGHeroInstance * ourHero = owner.cb->getBattle(br.battleID)->battleGetMyHero();
|
const CGHeroInstance * ourHero = owner.cb->getBattle(br.battleID)->battleGetMyHero();
|
||||||
if (ourHero)
|
if (ourHero)
|
||||||
{
|
{
|
||||||
str += CGI->generaltexth->allTexts[305];
|
resources.resultText.appendTextID("core.genrltxt.305");
|
||||||
boost::algorithm::replace_first(str, "%s", ourHero->getNameTranslated());
|
resources.resultText.replaceTextID(ourHero->getNameTranslated());
|
||||||
boost::algorithm::replace_first(str, "%d", std::to_string(br.exp[weAreAttacker ? 0 : 1]));
|
resources.resultText.replaceNumber(br.exp[weAreAttacker ? 0 : 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
description = std::make_shared<CTextBox>(str, Rect(69, 203, 330, 68), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
|
|
||||||
}
|
}
|
||||||
else // we lose
|
else // we lose
|
||||||
{
|
{
|
||||||
int text = 311;
|
|
||||||
currentVideo = BattleResultVideo::DEFEAT;
|
|
||||||
switch(br.result)
|
switch(br.result)
|
||||||
{
|
{
|
||||||
case EBattleResult::NORMAL:
|
case EBattleResult::NORMAL:
|
||||||
if(owner.cb->getBattle(br.battleID)->battleGetDefendedTown() && !weAreAttacker)
|
resources.resultText.appendTextID("core.genrltxt.311");
|
||||||
currentVideo = BattleResultVideo::DEFEAT_SIEGE;
|
resources.musicName = AudioPath::builtin("Music/LoseCombat");
|
||||||
|
resources.prologueVideo = VideoPath::builtin("LBSTART.BIK");
|
||||||
|
resources.loopedVideo = VideoPath::builtin("LBLOOP.BIK");
|
||||||
break;
|
break;
|
||||||
case EBattleResult::ESCAPE:
|
case EBattleResult::ESCAPE:
|
||||||
currentVideo = BattleResultVideo::RETREAT;
|
resources.resultText.appendTextID("core.genrltxt.310");
|
||||||
text = 310;
|
resources.musicName = AudioPath::builtin("Music/Retreat Battle");
|
||||||
|
resources.prologueVideo = VideoPath::builtin("RTSTART.BIK");
|
||||||
|
resources.loopedVideo = VideoPath::builtin("RTLOOP.BIK");
|
||||||
break;
|
break;
|
||||||
case EBattleResult::SURRENDER:
|
case EBattleResult::SURRENDER:
|
||||||
currentVideo = BattleResultVideo::SURRENDER;
|
resources.resultText.appendTextID("core.genrltxt.309");
|
||||||
text = 309;
|
resources.musicName = AudioPath::builtin("Music/Surrender Battle");
|
||||||
|
resources.prologueVideo = VideoPath();
|
||||||
|
resources.loopedVideo = VideoPath::builtin("SURRENDER.BIK");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
logGlobal->error("Invalid battle result code %d. Assumed normal.", static_cast<int>(br.result));
|
throw std::runtime_error("Invalid battle result!");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
playVideo();
|
|
||||||
|
|
||||||
labels.push_back(std::make_shared<CLabel>(235, 235, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[text]));
|
if(isSiege && weAreDefender)
|
||||||
|
{
|
||||||
|
resources.musicName = AudioPath::builtin("Music/LoseCastle");
|
||||||
|
resources.prologueVideo = VideoPath::builtin("LOSECSTL.BIK");
|
||||||
|
resources.loopedVideo = VideoPath::builtin("LOSECSLP.BIK");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleResultWindow::activate()
|
void BattleResultWindow::activate()
|
||||||
@@ -775,81 +805,6 @@ void BattleResultWindow::activate()
|
|||||||
CIntObject::activate();
|
CIntObject::activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleResultWindow::show(Canvas & to)
|
|
||||||
{
|
|
||||||
CIntObject::show(to);
|
|
||||||
CCS->videoh->update(pos.x + 107, pos.y + 70, to.getInternalSurface(), true, false,
|
|
||||||
[&]()
|
|
||||||
{
|
|
||||||
playVideo(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void BattleResultWindow::playVideo(bool startLoop)
|
|
||||||
{
|
|
||||||
AudioPath musicName = AudioPath();
|
|
||||||
VideoPath videoName = VideoPath();
|
|
||||||
|
|
||||||
if(!startLoop)
|
|
||||||
{
|
|
||||||
switch(currentVideo)
|
|
||||||
{
|
|
||||||
case BattleResultVideo::WIN:
|
|
||||||
musicName = AudioPath::builtin("Music/Win Battle");
|
|
||||||
videoName = VideoPath::builtin("WIN3.BIK");
|
|
||||||
break;
|
|
||||||
case BattleResultVideo::SURRENDER:
|
|
||||||
musicName = AudioPath::builtin("Music/Surrender Battle");
|
|
||||||
videoName = VideoPath::builtin("SURRENDER.BIK");
|
|
||||||
break;
|
|
||||||
case BattleResultVideo::RETREAT:
|
|
||||||
musicName = AudioPath::builtin("Music/Retreat Battle");
|
|
||||||
videoName = VideoPath::builtin("RTSTART.BIK");
|
|
||||||
break;
|
|
||||||
case BattleResultVideo::DEFEAT:
|
|
||||||
musicName = AudioPath::builtin("Music/LoseCombat");
|
|
||||||
videoName = VideoPath::builtin("LBSTART.BIK");
|
|
||||||
break;
|
|
||||||
case BattleResultVideo::DEFEAT_SIEGE:
|
|
||||||
musicName = AudioPath::builtin("Music/LoseCastle");
|
|
||||||
videoName = VideoPath::builtin("LOSECSTL.BIK");
|
|
||||||
break;
|
|
||||||
case BattleResultVideo::WIN_SIEGE:
|
|
||||||
musicName = AudioPath::builtin("Music/Defend Castle");
|
|
||||||
videoName = VideoPath::builtin("DEFENDALL.BIK");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch(currentVideo)
|
|
||||||
{
|
|
||||||
case BattleResultVideo::RETREAT:
|
|
||||||
currentVideo = BattleResultVideo::RETREAT_LOOP;
|
|
||||||
videoName = VideoPath::builtin("RTLOOP.BIK");
|
|
||||||
break;
|
|
||||||
case BattleResultVideo::DEFEAT:
|
|
||||||
currentVideo = BattleResultVideo::DEFEAT_LOOP;
|
|
||||||
videoName = VideoPath::builtin("LBLOOP.BIK");
|
|
||||||
break;
|
|
||||||
case BattleResultVideo::DEFEAT_SIEGE:
|
|
||||||
currentVideo = BattleResultVideo::DEFEAT_SIEGE_LOOP;
|
|
||||||
videoName = VideoPath::builtin("LOSECSLP.BIK");
|
|
||||||
break;
|
|
||||||
case BattleResultVideo::WIN_SIEGE:
|
|
||||||
currentVideo = BattleResultVideo::WIN_SIEGE_LOOP;
|
|
||||||
videoName = VideoPath::builtin("DEFENDLOOP.BIK");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(musicName != AudioPath())
|
|
||||||
CCS->musich->playMusic(musicName, false, true);
|
|
||||||
|
|
||||||
if(videoName != VideoPath())
|
|
||||||
CCS->videoh->open(videoName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BattleResultWindow::buttonPressed(int button)
|
void BattleResultWindow::buttonPressed(int button)
|
||||||
{
|
{
|
||||||
if (resultCallback)
|
if (resultCallback)
|
||||||
@@ -865,7 +820,6 @@ void BattleResultWindow::buttonPressed(int button)
|
|||||||
//Result window and battle interface are gone. We requested all dialogs to be closed before opening the battle,
|
//Result window and battle interface are gone. We requested all dialogs to be closed before opening the battle,
|
||||||
//so we can be sure that there is no dialogs left on GUI stack.
|
//so we can be sure that there is no dialogs left on GUI stack.
|
||||||
intTmp.showingDialog->setn(false);
|
intTmp.showingDialog->setn(false);
|
||||||
CCS->videoh->close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BattleResultWindow::bExitf()
|
void BattleResultWindow::bExitf()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "../../lib/FunctionList.h"
|
#include "../../lib/FunctionList.h"
|
||||||
#include "../../lib/battle/BattleHex.h"
|
#include "../../lib/battle/BattleHex.h"
|
||||||
#include "../windows/CWindowObject.h"
|
#include "../windows/CWindowObject.h"
|
||||||
|
#include "../../lib/MetaString.h"
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ class CAnimImage;
|
|||||||
class TransparentFilledRectangle;
|
class TransparentFilledRectangle;
|
||||||
class CPlayerInterface;
|
class CPlayerInterface;
|
||||||
class BattleRenderer;
|
class BattleRenderer;
|
||||||
|
class VideoWidget;
|
||||||
|
|
||||||
/// Class which shows the console at the bottom of the battle screen and manages the text of the console
|
/// Class which shows the console at the bottom of the battle screen and manages the text of the console
|
||||||
class BattleConsole : public CIntObject, public IStatusBar
|
class BattleConsole : public CIntObject, public IStatusBar
|
||||||
@@ -185,6 +187,14 @@ public:
|
|||||||
HeroInfoWindow(const InfoAboutHero & hero, Point * position);
|
HeroInfoWindow(const InfoAboutHero & hero, Point * position);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct BattleResultResources
|
||||||
|
{
|
||||||
|
VideoPath prologueVideo;
|
||||||
|
VideoPath loopedVideo;
|
||||||
|
AudioPath musicName;
|
||||||
|
MetaString resultText;
|
||||||
|
};
|
||||||
|
|
||||||
/// Class which is responsible for showing the battle result window
|
/// Class which is responsible for showing the battle result window
|
||||||
class BattleResultWindow : public WindowBase
|
class BattleResultWindow : public WindowBase
|
||||||
{
|
{
|
||||||
@@ -195,25 +205,10 @@ private:
|
|||||||
std::shared_ptr<CButton> repeat;
|
std::shared_ptr<CButton> repeat;
|
||||||
std::vector<std::shared_ptr<CAnimImage>> icons;
|
std::vector<std::shared_ptr<CAnimImage>> icons;
|
||||||
std::shared_ptr<CTextBox> description;
|
std::shared_ptr<CTextBox> description;
|
||||||
|
std::shared_ptr<VideoWidget> videoPlayer;
|
||||||
CPlayerInterface & owner;
|
CPlayerInterface & owner;
|
||||||
|
|
||||||
enum BattleResultVideo
|
BattleResultResources getResources(const BattleResult & br);
|
||||||
{
|
|
||||||
NONE,
|
|
||||||
WIN,
|
|
||||||
SURRENDER,
|
|
||||||
RETREAT,
|
|
||||||
RETREAT_LOOP,
|
|
||||||
DEFEAT,
|
|
||||||
DEFEAT_LOOP,
|
|
||||||
DEFEAT_SIEGE,
|
|
||||||
DEFEAT_SIEGE_LOOP,
|
|
||||||
WIN_SIEGE,
|
|
||||||
WIN_SIEGE_LOOP,
|
|
||||||
};
|
|
||||||
BattleResultVideo currentVideo;
|
|
||||||
|
|
||||||
void playVideo(bool startLoop = false);
|
|
||||||
|
|
||||||
void buttonPressed(int button); //internal function for button callbacks
|
void buttonPressed(int button); //internal function for button callbacks
|
||||||
public:
|
public:
|
||||||
@@ -224,7 +219,6 @@ public:
|
|||||||
std::function<void(int result)> resultCallback; //callback receiving which button was pressed
|
std::function<void(int result)> resultCallback; //callback receiving which button was pressed
|
||||||
|
|
||||||
void activate() override;
|
void activate() override;
|
||||||
void show(Canvas & to) override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Shows the stack queue
|
/// Shows the stack queue
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
#include "BattleRenderer.h"
|
#include "BattleRenderer.h"
|
||||||
#include "CreatureAnimation.h"
|
#include "CreatureAnimation.h"
|
||||||
|
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
#include "../render/IRenderHandler.h"
|
#include "../render/IRenderHandler.h"
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
#include "BattleFieldController.h"
|
#include "BattleFieldController.h"
|
||||||
#include "BattleRenderer.h"
|
#include "BattleRenderer.h"
|
||||||
|
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
#include "../render/IImage.h"
|
#include "../render/IImage.h"
|
||||||
#include "../render/IRenderHandler.h"
|
#include "../render/IRenderHandler.h"
|
||||||
|
|||||||
@@ -23,10 +23,10 @@
|
|||||||
#include "CreatureAnimation.h"
|
#include "CreatureAnimation.h"
|
||||||
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../render/Colors.h"
|
#include "../render/Colors.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
#include "../render/IRenderHandler.h"
|
#include "../render/IRenderHandler.h"
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../gui/CursorHandler.h"
|
#include "../gui/CursorHandler.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
|
|||||||
@@ -22,10 +22,11 @@
|
|||||||
#include "../gui/CursorHandler.h"
|
#include "../gui/CursorHandler.h"
|
||||||
#include "../gui/EventDispatcher.h"
|
#include "../gui/EventDispatcher.h"
|
||||||
#include "../gui/MouseButton.h"
|
#include "../gui/MouseButton.h"
|
||||||
|
#include "../media/IMusicPlayer.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../CMT.h"
|
#include "../CMT.h"
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
|
|
||||||
#include "../../lib/CConfigHandler.h"
|
#include "../../lib/CConfigHandler.h"
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
#include "GlobalLobbyWindow.h"
|
#include "GlobalLobbyWindow.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CServerHandler.h"
|
#include "../CServerHandler.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
#include "../mainmenu/CMainMenu.h"
|
#include "../mainmenu/CMainMenu.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../windows/InfoWindows.h"
|
#include "../windows/InfoWindows.h"
|
||||||
|
|
||||||
#include "../../lib/CConfigHandler.h"
|
#include "../../lib/CConfigHandler.h"
|
||||||
|
|||||||
@@ -16,10 +16,10 @@
|
|||||||
#include "GlobalLobbyRoomWindow.h"
|
#include "GlobalLobbyRoomWindow.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CServerHandler.h"
|
#include "../CServerHandler.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../render/Colors.h"
|
#include "../render/Colors.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/CTextInput.h"
|
#include "../widgets/CTextInput.h"
|
||||||
|
|||||||
@@ -18,12 +18,11 @@
|
|||||||
#include "ExtraOptionsTab.h"
|
#include "ExtraOptionsTab.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CVideoHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../CServerHandler.h"
|
#include "../CServerHandler.h"
|
||||||
#include "../mainmenu/CMainMenu.h"
|
#include "../mainmenu/CMainMenu.h"
|
||||||
#include "../mainmenu/CPrologEpilogVideo.h"
|
#include "../mainmenu/CPrologEpilogVideo.h"
|
||||||
|
#include "../media/IMusicPlayer.h"
|
||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/MiscWidgets.h"
|
#include "../widgets/MiscWidgets.h"
|
||||||
@@ -41,9 +40,9 @@
|
|||||||
|
|
||||||
#include "../../lib/filesystem/Filesystem.h"
|
#include "../../lib/filesystem/Filesystem.h"
|
||||||
#include "../../lib/CGeneralTextHandler.h"
|
#include "../../lib/CGeneralTextHandler.h"
|
||||||
|
#include "../../lib/CConfigHandler.h"
|
||||||
#include "../../lib/CBuildingHandler.h"
|
#include "../../lib/CBuildingHandler.h"
|
||||||
|
#include "../../lib/CConfigHandler.h"
|
||||||
#include "../../lib/CSkillHandler.h"
|
#include "../../lib/CSkillHandler.h"
|
||||||
#include "../../lib/CTownHandler.h"
|
#include "../../lib/CTownHandler.h"
|
||||||
#include "../../lib/CHeroHandler.h"
|
#include "../../lib/CHeroHandler.h"
|
||||||
|
|||||||
@@ -20,14 +20,13 @@
|
|||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CVideoHandler.h"
|
|
||||||
#include "../CServerHandler.h"
|
#include "../CServerHandler.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
#include "../globalLobby/GlobalLobbyClient.h"
|
#include "../globalLobby/GlobalLobbyClient.h"
|
||||||
#include "../mainmenu/CMainMenu.h"
|
#include "../mainmenu/CMainMenu.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
#include "../widgets/CTextInput.h"
|
#include "../widgets/CTextInput.h"
|
||||||
|
|||||||
@@ -14,12 +14,12 @@
|
|||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CServerHandler.h"
|
#include "../CServerHandler.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
#include "../render/Graphics.h"
|
#include "../render/Graphics.h"
|
||||||
#include "../render/IFont.h"
|
#include "../render/IFont.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
#include "../widgets/ComboBox.h"
|
#include "../widgets/ComboBox.h"
|
||||||
#include "../widgets/CTextInput.h"
|
#include "../widgets/CTextInput.h"
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
#include "../../lib/networkPacks/PacksForLobby.h"
|
#include "../../lib/networkPacks/PacksForLobby.h"
|
||||||
#include "../../lib/CGeneralTextHandler.h"
|
#include "../../lib/CGeneralTextHandler.h"
|
||||||
#include "../../lib/CArtHandler.h"
|
#include "../../lib/CArtHandler.h"
|
||||||
|
#include "../../lib/CConfigHandler.h"
|
||||||
#include "../../lib/CTownHandler.h"
|
#include "../../lib/CTownHandler.h"
|
||||||
#include "../../lib/CHeroHandler.h"
|
#include "../../lib/CHeroHandler.h"
|
||||||
#include "../../lib/mapping/CMapInfo.h"
|
#include "../../lib/mapping/CMapInfo.h"
|
||||||
|
|||||||
@@ -14,18 +14,18 @@
|
|||||||
#include "CMainMenu.h"
|
#include "CMainMenu.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CVideoHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../CServerHandler.h"
|
#include "../CServerHandler.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
|
#include "../media/IMusicPlayer.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/MiscWidgets.h"
|
#include "../widgets/MiscWidgets.h"
|
||||||
#include "../widgets/ObjectLists.h"
|
#include "../widgets/ObjectLists.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
|
#include "../widgets/VideoWidget.h"
|
||||||
#include "../windows/GUIClasses.h"
|
#include "../windows/GUIClasses.h"
|
||||||
#include "../windows/InfoWindows.h"
|
#include "../windows/InfoWindows.h"
|
||||||
#include "../windows/CWindowObject.h"
|
#include "../windows/CWindowObject.h"
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
#include "../../lib/CArtHandler.h"
|
#include "../../lib/CArtHandler.h"
|
||||||
#include "../../lib/CBuildingHandler.h"
|
#include "../../lib/CBuildingHandler.h"
|
||||||
#include "../../lib/spells/CSpellHandler.h"
|
#include "../../lib/spells/CSpellHandler.h"
|
||||||
|
#include "../../lib/CConfigHandler.h"
|
||||||
#include "../../lib/CSkillHandler.h"
|
#include "../../lib/CSkillHandler.h"
|
||||||
#include "../../lib/CTownHandler.h"
|
#include "../../lib/CTownHandler.h"
|
||||||
#include "../../lib/CHeroHandler.h"
|
#include "../../lib/CHeroHandler.h"
|
||||||
@@ -100,7 +100,7 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config, const
|
|||||||
pos.h = 116;
|
pos.h = 116;
|
||||||
|
|
||||||
campFile = config["file"].String();
|
campFile = config["file"].String();
|
||||||
video = VideoPath::fromJson(config["video"]);
|
videoPath = VideoPath::fromJson(config["video"]);
|
||||||
|
|
||||||
status = CCampaignScreen::ENABLED;
|
status = CCampaignScreen::ENABLED;
|
||||||
|
|
||||||
@@ -127,7 +127,6 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config, const
|
|||||||
{
|
{
|
||||||
addUsedEvents(LCLICK | HOVER);
|
addUsedEvents(LCLICK | HOVER);
|
||||||
graphicsImage = std::make_shared<CPicture>(ImagePath::fromJson(config["image"]));
|
graphicsImage = std::make_shared<CPicture>(ImagePath::fromJson(config["image"]));
|
||||||
|
|
||||||
hoverLabel = std::make_shared<CLabel>(pos.w / 2, pos.h + 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, "");
|
hoverLabel = std::make_shared<CLabel>(pos.w / 2, pos.h + 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, "");
|
||||||
parent->addChild(hoverLabel.get());
|
parent->addChild(hoverLabel.get());
|
||||||
}
|
}
|
||||||
@@ -136,30 +135,19 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config, const
|
|||||||
graphicsCompleted = std::make_shared<CPicture>(ImagePath::builtin("CAMPCHK"));
|
graphicsCompleted = std::make_shared<CPicture>(ImagePath::builtin("CAMPCHK"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCampaignScreen::CCampaignButton::show(Canvas & to)
|
|
||||||
{
|
|
||||||
if(status == CCampaignScreen::DISABLED)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CIntObject::show(to);
|
|
||||||
|
|
||||||
// Play the campaign button video when the mouse cursor is placed over the button
|
|
||||||
if(isHovered())
|
|
||||||
CCS->videoh->update(pos.x, pos.y, to.getInternalSurface(), true, false); // plays sequentially frame by frame, starts at the beginning when the video is over
|
|
||||||
}
|
|
||||||
|
|
||||||
void CCampaignScreen::CCampaignButton::clickReleased(const Point & cursorPosition)
|
void CCampaignScreen::CCampaignButton::clickReleased(const Point & cursorPosition)
|
||||||
{
|
{
|
||||||
CCS->videoh->close();
|
|
||||||
CMainMenu::openCampaignLobby(campFile, campaignSet);
|
CMainMenu::openCampaignLobby(campFile, campaignSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCampaignScreen::CCampaignButton::hover(bool on)
|
void CCampaignScreen::CCampaignButton::hover(bool on)
|
||||||
{
|
{
|
||||||
if (on)
|
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||||
CCS->videoh->open(video);
|
|
||||||
|
if (on && !videoPath.empty())
|
||||||
|
videoPlayer = std::make_shared<VideoWidget>(Point(), videoPath, false);
|
||||||
else
|
else
|
||||||
CCS->videoh->close();
|
videoPlayer.reset();
|
||||||
|
|
||||||
if(hoverLabel)
|
if(hoverLabel)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ VCMI_LIB_NAMESPACE_END
|
|||||||
class CLabel;
|
class CLabel;
|
||||||
class CPicture;
|
class CPicture;
|
||||||
class CButton;
|
class CButton;
|
||||||
|
class VideoWidget;
|
||||||
|
|
||||||
class CCampaignScreen : public CWindowObject
|
class CCampaignScreen : public CWindowObject
|
||||||
{
|
{
|
||||||
@@ -34,10 +35,11 @@ private:
|
|||||||
std::shared_ptr<CLabel> hoverLabel;
|
std::shared_ptr<CLabel> hoverLabel;
|
||||||
std::shared_ptr<CPicture> graphicsImage;
|
std::shared_ptr<CPicture> graphicsImage;
|
||||||
std::shared_ptr<CPicture> graphicsCompleted;
|
std::shared_ptr<CPicture> graphicsCompleted;
|
||||||
|
std::shared_ptr<VideoWidget> videoPlayer;
|
||||||
CampaignStatus status;
|
CampaignStatus status;
|
||||||
|
VideoPath videoPath;
|
||||||
|
|
||||||
std::string campFile; // the filename/resourcename of the campaign
|
std::string campFile; // the filename/resourcename of the campaign
|
||||||
VideoPath video; // the resource name of the video
|
|
||||||
std::string hoverText;
|
std::string hoverText;
|
||||||
|
|
||||||
std::string campaignSet;
|
std::string campaignSet;
|
||||||
@@ -47,7 +49,6 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
CCampaignButton(const JsonNode & config, const JsonNode & parentConfig, std::string campaignSet);
|
CCampaignButton(const JsonNode & config, const JsonNode & parentConfig, std::string campaignSet);
|
||||||
void show(Canvas & to) override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string campaignSet;
|
std::string campaignSet;
|
||||||
|
|||||||
@@ -14,17 +14,18 @@
|
|||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
|
#include "../media/IMusicPlayer.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/CTextInput.h"
|
#include "../widgets/CTextInput.h"
|
||||||
#include "../widgets/Images.h"
|
#include "../widgets/Images.h"
|
||||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||||
|
#include "../widgets/VideoWidget.h"
|
||||||
#include "../windows/InfoWindows.h"
|
#include "../windows/InfoWindows.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CVideoHandler.h"
|
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../../lib/CGeneralTextHandler.h"
|
#include "../../lib/CGeneralTextHandler.h"
|
||||||
#include "../../lib/CConfigHandler.h"
|
#include "../../lib/CConfigHandler.h"
|
||||||
#include "../../lib/CCreatureHandler.h"
|
#include "../../lib/CCreatureHandler.h"
|
||||||
@@ -216,7 +217,7 @@ void CHighScoreScreen::buttonExitClick()
|
|||||||
}
|
}
|
||||||
|
|
||||||
CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc)
|
CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc)
|
||||||
: CWindowObject(BORDERED), won(won), calc(calc), videoSoundHandle(-1)
|
: CWindowObject(BORDERED), won(won), calc(calc)
|
||||||
{
|
{
|
||||||
addUsedEvents(LCLICK | KEYBOARD);
|
addUsedEvents(LCLICK | KEYBOARD);
|
||||||
|
|
||||||
@@ -228,6 +229,9 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc
|
|||||||
|
|
||||||
if(won)
|
if(won)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
videoPlayer = std::make_shared<VideoWidget>(Point(0, 0), VideoPath::builtin("HSANIM.SMK"), VideoPath::builtin("HSLOOP.SMK"), true);
|
||||||
|
|
||||||
int border = 100;
|
int border = 100;
|
||||||
int textareaW = ((pos.w - 2 * border) / 4);
|
int textareaW = ((pos.w - 2 * border) / 4);
|
||||||
std::vector<std::string> t = { "438", "439", "440", "441", "676" }; // time, score, difficulty, final score, rank
|
std::vector<std::string> t = { "438", "439", "440", "441", "676" }; // time, score, difficulty, final score, rank
|
||||||
@@ -242,9 +246,10 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc
|
|||||||
CCS->musich->playMusic(AudioPath::builtin("music/Win Scenario"), true, true);
|
CCS->musich->playMusic(AudioPath::builtin("music/Win Scenario"), true, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
videoPlayer = std::make_shared<VideoWidgetOnce>(Point(0, 0), VideoPath::builtin("LOSEGAME.SMK"), true, [this](){close();});
|
||||||
CCS->musich->playMusic(AudioPath::builtin("music/UltimateLose"), false, true);
|
CCS->musich->playMusic(AudioPath::builtin("music/UltimateLose"), false, true);
|
||||||
|
}
|
||||||
video = won ? "HSANIM.SMK" : "LOSEGAME.SMK";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int CHighScoreInputScreen::addEntry(std::string text) {
|
int CHighScoreInputScreen::addEntry(std::string text) {
|
||||||
@@ -289,43 +294,7 @@ int CHighScoreInputScreen::addEntry(std::string text) {
|
|||||||
|
|
||||||
void CHighScoreInputScreen::show(Canvas & to)
|
void CHighScoreInputScreen::show(Canvas & to)
|
||||||
{
|
{
|
||||||
CCS->videoh->update(pos.x, pos.y, to.getInternalSurface(), true, false,
|
CWindowObject::showAll(to);
|
||||||
[&]()
|
|
||||||
{
|
|
||||||
if(won)
|
|
||||||
{
|
|
||||||
CCS->videoh->close();
|
|
||||||
video = "HSLOOP.SMK";
|
|
||||||
auto audioData = CCS->videoh->getAudio(VideoPath::builtin(video));
|
|
||||||
videoSoundHandle = CCS->soundh->playSound(audioData);
|
|
||||||
CCS->videoh->open(VideoPath::builtin(video));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
close();
|
|
||||||
});
|
|
||||||
redraw();
|
|
||||||
|
|
||||||
CIntObject::show(to);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CHighScoreInputScreen::activate()
|
|
||||||
{
|
|
||||||
auto audioData = CCS->videoh->getAudio(VideoPath::builtin(video));
|
|
||||||
videoSoundHandle = CCS->soundh->playSound(audioData);
|
|
||||||
if(!CCS->videoh->open(VideoPath::builtin(video)))
|
|
||||||
{
|
|
||||||
if(!won)
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
background = nullptr;
|
|
||||||
CIntObject::activate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CHighScoreInputScreen::deactivate()
|
|
||||||
{
|
|
||||||
CCS->videoh->close();
|
|
||||||
CCS->soundh->stopSound(videoSoundHandle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CHighScoreInputScreen::clickPressed(const Point & cursorPosition)
|
void CHighScoreInputScreen::clickPressed(const Point & cursorPosition)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class CLabel;
|
|||||||
class CMultiLineLabel;
|
class CMultiLineLabel;
|
||||||
class CAnimImage;
|
class CAnimImage;
|
||||||
class CTextInput;
|
class CTextInput;
|
||||||
|
class VideoWidgetBase;
|
||||||
|
|
||||||
class TransparentFilledRectangle;
|
class TransparentFilledRectangle;
|
||||||
|
|
||||||
@@ -93,9 +94,8 @@ class CHighScoreInputScreen : public CWindowObject
|
|||||||
std::vector<std::shared_ptr<CMultiLineLabel>> texts;
|
std::vector<std::shared_ptr<CMultiLineLabel>> texts;
|
||||||
std::shared_ptr<CHighScoreInput> input;
|
std::shared_ptr<CHighScoreInput> input;
|
||||||
std::shared_ptr<TransparentFilledRectangle> background;
|
std::shared_ptr<TransparentFilledRectangle> background;
|
||||||
|
std::shared_ptr<VideoWidgetBase> videoPlayer;
|
||||||
|
|
||||||
std::string video;
|
|
||||||
int videoSoundHandle;
|
|
||||||
bool won;
|
bool won;
|
||||||
HighScoreCalculation calc;
|
HighScoreCalculation calc;
|
||||||
public:
|
public:
|
||||||
@@ -103,9 +103,7 @@ public:
|
|||||||
|
|
||||||
int addEntry(std::string text);
|
int addEntry(std::string text);
|
||||||
|
|
||||||
void show(Canvas & to) override;
|
|
||||||
void activate() override;
|
|
||||||
void deactivate() override;
|
|
||||||
void clickPressed(const Point & cursorPosition) override;
|
void clickPressed(const Point & cursorPosition) override;
|
||||||
void keyPressed(EShortcut key) override;
|
void keyPressed(EShortcut key) override;
|
||||||
};
|
void show(Canvas & to) override;
|
||||||
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "../lobby/CBonusSelection.h"
|
#include "../lobby/CBonusSelection.h"
|
||||||
#include "../lobby/CSelectionBase.h"
|
#include "../lobby/CSelectionBase.h"
|
||||||
#include "../lobby/CLobbyScreen.h"
|
#include "../lobby/CLobbyScreen.h"
|
||||||
|
#include "../media/IMusicPlayer.h"
|
||||||
#include "../gui/CursorHandler.h"
|
#include "../gui/CursorHandler.h"
|
||||||
#include "../windows/GUIClasses.h"
|
#include "../windows/GUIClasses.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
@@ -33,12 +34,11 @@
|
|||||||
#include "../widgets/MiscWidgets.h"
|
#include "../widgets/MiscWidgets.h"
|
||||||
#include "../widgets/ObjectLists.h"
|
#include "../widgets/ObjectLists.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
|
#include "../widgets/VideoWidget.h"
|
||||||
#include "../windows/InfoWindows.h"
|
#include "../windows/InfoWindows.h"
|
||||||
#include "../CServerHandler.h"
|
#include "../CServerHandler.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CVideoHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../Client.h"
|
#include "../Client.h"
|
||||||
#include "../CMT.h"
|
#include "../CMT.h"
|
||||||
@@ -92,8 +92,14 @@ CMenuScreen::CMenuScreen(const JsonNode & configNode)
|
|||||||
menuNameToEntry.push_back("credits");
|
menuNameToEntry.push_back("credits");
|
||||||
|
|
||||||
tabs = std::make_shared<CTabbedInt>(std::bind(&CMenuScreen::createTab, this, _1));
|
tabs = std::make_shared<CTabbedInt>(std::bind(&CMenuScreen::createTab, this, _1));
|
||||||
if(config["video"].isNull())
|
if(!config["video"].isNull())
|
||||||
|
{
|
||||||
|
Point videoPosition(config["video"]["x"].Integer(), config["video"]["y"].Integer());
|
||||||
|
videoPlayer = std::make_shared<VideoWidget>(videoPosition, VideoPath::fromJson(config["video"]["name"]), false);
|
||||||
|
}
|
||||||
|
else
|
||||||
tabs->setRedrawParent(true);
|
tabs->setRedrawParent(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<CIntObject> CMenuScreen::createTab(size_t index)
|
std::shared_ptr<CIntObject> CMenuScreen::createTab(size_t index)
|
||||||
@@ -106,32 +112,16 @@ std::shared_ptr<CIntObject> CMenuScreen::createTab(size_t index)
|
|||||||
|
|
||||||
void CMenuScreen::show(Canvas & to)
|
void CMenuScreen::show(Canvas & to)
|
||||||
{
|
{
|
||||||
if(!config["video"].isNull())
|
// TODO: avoid excessive redraws
|
||||||
{
|
CIntObject::showAll(to);
|
||||||
// redraw order: background -> video -> buttons and pictures
|
|
||||||
background->redraw();
|
|
||||||
CCS->videoh->update((int)config["video"]["x"].Float() + pos.x, (int)config["video"]["y"].Float() + pos.y, to.getInternalSurface(), true, false);
|
|
||||||
tabs->redraw();
|
|
||||||
}
|
|
||||||
CIntObject::show(to);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CMenuScreen::activate()
|
void CMenuScreen::activate()
|
||||||
{
|
{
|
||||||
CCS->musich->playMusic(AudioPath::builtin("Music/MainMenu"), true, true);
|
CCS->musich->playMusic(AudioPath::builtin("Music/MainMenu"), true, true);
|
||||||
if(!config["video"].isNull())
|
|
||||||
CCS->videoh->open(VideoPath::fromJson(config["video"]["name"]));
|
|
||||||
CIntObject::activate();
|
CIntObject::activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CMenuScreen::deactivate()
|
|
||||||
{
|
|
||||||
if(!config["video"].isNull())
|
|
||||||
CCS->videoh->close();
|
|
||||||
|
|
||||||
CIntObject::deactivate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CMenuScreen::switchToTab(size_t index)
|
void CMenuScreen::switchToTab(size_t index)
|
||||||
{
|
{
|
||||||
tabs->setActive(index);
|
tabs->setActive(index);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class CAnimation;
|
|||||||
class CButton;
|
class CButton;
|
||||||
class CFilledTexture;
|
class CFilledTexture;
|
||||||
class CLabel;
|
class CLabel;
|
||||||
|
class VideoWidget;
|
||||||
|
|
||||||
// TODO: Find new location for these enums
|
// TODO: Find new location for these enums
|
||||||
enum class ESelectionScreen : ui8 {
|
enum class ESelectionScreen : ui8 {
|
||||||
@@ -48,6 +48,7 @@ class CMenuScreen : public CWindowObject
|
|||||||
std::shared_ptr<CTabbedInt> tabs;
|
std::shared_ptr<CTabbedInt> tabs;
|
||||||
|
|
||||||
std::shared_ptr<CPicture> background;
|
std::shared_ptr<CPicture> background;
|
||||||
|
std::shared_ptr<VideoWidget> videoPlayer;
|
||||||
std::vector<std::shared_ptr<CPicture>> images;
|
std::vector<std::shared_ptr<CPicture>> images;
|
||||||
|
|
||||||
std::shared_ptr<CIntObject> createTab(size_t index);
|
std::shared_ptr<CIntObject> createTab(size_t index);
|
||||||
@@ -57,9 +58,8 @@ public:
|
|||||||
|
|
||||||
CMenuScreen(const JsonNode & configNode);
|
CMenuScreen(const JsonNode & configNode);
|
||||||
|
|
||||||
void show(Canvas & to) override;
|
|
||||||
void activate() override;
|
void activate() override;
|
||||||
void deactivate() override;
|
void show(Canvas & to) override;
|
||||||
|
|
||||||
void switchToTab(size_t index);
|
void switchToTab(size_t index);
|
||||||
void switchToTab(std::string name);
|
void switchToTab(std::string name);
|
||||||
|
|||||||
@@ -12,15 +12,15 @@
|
|||||||
|
|
||||||
#include "CPrologEpilogVideo.h"
|
#include "CPrologEpilogVideo.h"
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
#include "../media/IMusicPlayer.h"
|
||||||
#include "../CVideoHandler.h"
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../gui/WindowHandler.h"
|
//#include "../gui/WindowHandler.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/FramerateManager.h"
|
//#include "../gui/FramerateManager.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
|
#include "../widgets/VideoWidget.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
|
|
||||||
|
|
||||||
CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::function<void()> callback)
|
CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::function<void()> callback)
|
||||||
: CWindowObject(BORDERED), spe(_spe), positionCounter(0), voiceSoundHandle(-1), videoSoundHandle(-1), exitCb(callback), elapsedTimeMilliseconds(0)
|
: CWindowObject(BORDERED), spe(_spe), positionCounter(0), voiceSoundHandle(-1), videoSoundHandle(-1), exitCb(callback), elapsedTimeMilliseconds(0)
|
||||||
{
|
{
|
||||||
@@ -29,9 +29,23 @@ CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::f
|
|||||||
pos = center(Rect(0, 0, 800, 600));
|
pos = center(Rect(0, 0, 800, 600));
|
||||||
updateShadow();
|
updateShadow();
|
||||||
|
|
||||||
auto audioData = CCS->videoh->getAudio(spe.prologVideo);
|
//TODO: remove hardcoded paths. Some of campaigns video actually consist from 2 parts
|
||||||
videoSoundHandle = CCS->soundh->playSound(audioData, -1);
|
// however, currently our campaigns format expects only a single video file
|
||||||
CCS->videoh->open(spe.prologVideo);
|
static const std::map<VideoPath, VideoPath> pairedVideoFiles = {
|
||||||
|
{ VideoPath::builtin("EVIL2AP1"), VideoPath::builtin("EVIL2AP2") },
|
||||||
|
{ VideoPath::builtin("H3ABdb4"), VideoPath::builtin("H3ABdb4b") },
|
||||||
|
{ VideoPath::builtin("H3x2_RNe1"), VideoPath::builtin("H3x2_RNe2") },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pairedVideoFiles.count(spe.prologVideo))
|
||||||
|
videoPlayer = std::make_shared<VideoWidget>(Point(0, 0), spe.prologVideo, pairedVideoFiles.at(spe.prologVideo), true);
|
||||||
|
else
|
||||||
|
videoPlayer = std::make_shared<VideoWidget>(Point(0, 0), spe.prologVideo, true);
|
||||||
|
|
||||||
|
//some videos are 800x600 in size while some are 800x400
|
||||||
|
if (videoPlayer->pos.h == 400)
|
||||||
|
videoPlayer->moveBy(Point(0, 100));
|
||||||
|
|
||||||
CCS->musich->playMusic(spe.prologMusic, true, true);
|
CCS->musich->playMusic(spe.prologMusic, true, true);
|
||||||
voiceDurationMilliseconds = CCS->soundh->getSoundDurationMilliseconds(spe.prologVoice);
|
voiceDurationMilliseconds = CCS->soundh->getSoundDurationMilliseconds(spe.prologVoice);
|
||||||
voiceSoundHandle = CCS->soundh->playSound(spe.prologVoice);
|
voiceSoundHandle = CCS->soundh->playSound(spe.prologVoice);
|
||||||
@@ -65,9 +79,8 @@ void CPrologEpilogVideo::tick(uint32_t msPassed)
|
|||||||
void CPrologEpilogVideo::show(Canvas & to)
|
void CPrologEpilogVideo::show(Canvas & to)
|
||||||
{
|
{
|
||||||
to.drawColor(pos, Colors::BLACK);
|
to.drawColor(pos, Colors::BLACK);
|
||||||
//some videos are 800x600 in size while some are 800x400
|
|
||||||
CCS->videoh->update(pos.x, pos.y + (CCS->videoh->size().y == 400 ? 100 : 0), to.getInternalSurface(), true, false);
|
|
||||||
|
|
||||||
|
videoPlayer->show(to);
|
||||||
text->showAll(to); // blit text over video, if needed
|
text->showAll(to); // blit text over video, if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "../../lib/campaign/CampaignScenarioPrologEpilog.h"
|
#include "../../lib/campaign/CampaignScenarioPrologEpilog.h"
|
||||||
|
|
||||||
class CMultiLineLabel;
|
class CMultiLineLabel;
|
||||||
|
class VideoWidget;
|
||||||
|
|
||||||
class CPrologEpilogVideo : public CWindowObject
|
class CPrologEpilogVideo : public CWindowObject
|
||||||
{
|
{
|
||||||
@@ -25,6 +26,7 @@ class CPrologEpilogVideo : public CWindowObject
|
|||||||
std::function<void()> exitCb;
|
std::function<void()> exitCb;
|
||||||
|
|
||||||
std::shared_ptr<CMultiLineLabel> text;
|
std::shared_ptr<CMultiLineLabel> text;
|
||||||
|
std::shared_ptr<VideoWidget> videoPlayer;
|
||||||
|
|
||||||
bool voiceStopped = false;
|
bool voiceStopped = false;
|
||||||
|
|
||||||
|
|||||||
43
client/media/CAudioBase.cpp
Normal file
43
client/media/CAudioBase.cpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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 <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;
|
||||||
|
}
|
||||||
22
client/media/CAudioBase.h
Normal file
22
client/media/CAudioBase.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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 : boost::noncopyable
|
||||||
|
{
|
||||||
|
static int initializationCounter;
|
||||||
|
static bool initializeSuccess;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool isInitialized() const;
|
||||||
|
|
||||||
|
CAudioBase();
|
||||||
|
~CAudioBase();
|
||||||
|
};
|
||||||
38
client/media/CEmptyVideoPlayer.h
Normal file
38
client/media/CEmptyVideoPlayer.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
class CEmptyVideoPlayer final : public IVideoPlayer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Plays video on top of the screen, returns only after playback is over
|
||||||
|
bool playIntroVideo(const VideoPath & name) override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
void playSpellbookAnimation(const VideoPath & name, const Point & position) override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load video from specified path
|
||||||
|
std::unique_ptr<IVideoInstance> open(const VideoPath & name, bool scaleToScreen) override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Extracts audio data from provided video in wav format
|
||||||
|
std::pair<std::unique_ptr<ui8[]>, si64> getAudio(const VideoPath & videoToOpen) override
|
||||||
|
{
|
||||||
|
return {nullptr, 0};
|
||||||
|
};
|
||||||
|
};
|
||||||
349
client/media/CMusicHandler.cpp
Normal file
349
client/media/CMusicHandler.cpp
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
/*
|
||||||
|
* 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 "../eventsSDL/InputHandler.h"
|
||||||
|
#include "../gui/CGuiHandler.h"
|
||||||
|
#include "../renderSDL/SDLRWwrapper.h"
|
||||||
|
|
||||||
|
#include "../../lib/CRandomGenerator.h"
|
||||||
|
#include "../../lib/TerrainHandler.h"
|
||||||
|
#include "../../lib/filesystem/Filesystem.h"
|
||||||
|
|
||||||
|
#include <SDL_mixer.h>
|
||||||
|
|
||||||
|
void CMusicHandler::onVolumeChange(const JsonNode & volumeNode)
|
||||||
|
{
|
||||||
|
setVolume(volumeNode.Integer());
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == 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 != 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 != nullptr)
|
||||||
|
{
|
||||||
|
// if music is looped, play it again
|
||||||
|
if(current->play())
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
current.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(current == nullptr && next != 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)
|
||||||
|
, setName(std::move(setName))
|
||||||
|
, startTime(static_cast<uint32_t>(-1))
|
||||||
|
, startPosition(0)
|
||||||
|
, loop(looped ? -1 : 1)
|
||||||
|
, fromStart(fromStart)
|
||||||
|
, playing(false)
|
||||||
|
|
||||||
|
{
|
||||||
|
if(!musicURI.empty())
|
||||||
|
load(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() const
|
||||||
|
{
|
||||||
|
return playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MusicEntry::isSet(const std::string & set)
|
||||||
|
{
|
||||||
|
return !setName.empty() && set == setName;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MusicEntry::isTrack(const AudioPath & track)
|
||||||
|
{
|
||||||
|
return setName.empty() && track == currentName;
|
||||||
|
}
|
||||||
95
client/media/CMusicHandler.h
Normal file
95
client/media/CMusicHandler.h
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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 : boost::noncopyable
|
||||||
|
{
|
||||||
|
CMusicHandler * owner;
|
||||||
|
Mix_Music * music;
|
||||||
|
|
||||||
|
//if not null - set from which music will be randomly selected
|
||||||
|
std::string setName;
|
||||||
|
AudioPath currentName;
|
||||||
|
|
||||||
|
uint32_t startTime;
|
||||||
|
uint32_t startPosition;
|
||||||
|
int loop; // -1 = indefinite
|
||||||
|
bool fromStart;
|
||||||
|
bool playing;
|
||||||
|
|
||||||
|
void load(const AudioPath & musicURI);
|
||||||
|
|
||||||
|
public:
|
||||||
|
MusicEntry(CMusicHandler * owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart);
|
||||||
|
~MusicEntry();
|
||||||
|
|
||||||
|
bool isSet(const std::string & setName);
|
||||||
|
bool isTrack(const AudioPath & trackName);
|
||||||
|
bool isPlaying() const;
|
||||||
|
|
||||||
|
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) final;
|
||||||
|
|
||||||
|
friend class MusicEntry;
|
||||||
|
};
|
||||||
385
client/media/CSoundHandler.cpp
Normal file
385
client/media/CSoundHandler.cpp
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
/*
|
||||||
|
* 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 soundsList[] = {
|
||||||
|
"", // invalid
|
||||||
|
"", // todo
|
||||||
|
VCMI_SOUND_LIST
|
||||||
|
};
|
||||||
|
#undef VCMI_SOUND_NAME
|
||||||
|
#undef VCMI_SOUND_FILE
|
||||||
|
|
||||||
|
void CSoundHandler::onVolumeChange(const JsonNode & volumeNode)
|
||||||
|
{
|
||||||
|
setVolume(volumeNode.Integer());
|
||||||
|
}
|
||||||
|
|
||||||
|
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(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(), 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(static_cast<si64>(100), data.second));
|
||||||
|
|
||||||
|
if(cache && soundChunksRaw.find(startBytes) != soundChunksRaw.end())
|
||||||
|
return soundChunksRaw[startBytes].first;
|
||||||
|
|
||||||
|
SDL_RWops * ops = SDL_RWFromMem(data.first.get(), 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 volumeByDistance = static_cast<int>(distancesVector[distance].Integer());
|
||||||
|
return volumeByDistance * 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(), 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(soundsList[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(const auto & 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(const 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 ambientConfig["range"].Integer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSoundHandler::ambientUpdateChannels(std::map<AudioPath, int> soundsArg)
|
||||||
|
{
|
||||||
|
boost::mutex::scoped_lock guard(mutex);
|
||||||
|
|
||||||
|
std::vector<AudioPath> stoppedSounds;
|
||||||
|
for(const 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 channelVolume = ambientDistToVolume(soundsArg[soundId]);
|
||||||
|
channelVolumes[channel] = channelVolume;
|
||||||
|
updateChannelVolume(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(const auto & soundId : stoppedSounds)
|
||||||
|
{
|
||||||
|
channelVolumes.erase(ambientChannels[soundId]);
|
||||||
|
ambientChannels.erase(soundId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto & pair : soundsArg)
|
||||||
|
{
|
||||||
|
const auto & soundId = pair.first;
|
||||||
|
const int distance = pair.second;
|
||||||
|
|
||||||
|
if(!vstd::contains(ambientChannels, soundId))
|
||||||
|
{
|
||||||
|
int channel = playSound(soundId, -1);
|
||||||
|
int channelVolume = ambientDistToVolume(distance);
|
||||||
|
channelVolumes[channel] = channelVolume;
|
||||||
|
|
||||||
|
updateChannelVolume(channel);
|
||||||
|
ambientChannels[soundId] = channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSoundHandler::ambientStopAllChannels()
|
||||||
|
{
|
||||||
|
boost::mutex::scoped_lock guard(mutex);
|
||||||
|
|
||||||
|
for(const auto & ch : ambientChannels)
|
||||||
|
{
|
||||||
|
ambientStopSound(ch.first);
|
||||||
|
}
|
||||||
|
channelVolumes.clear();
|
||||||
|
ambientChannels.clear();
|
||||||
|
}
|
||||||
78
client/media/CSoundHandler.h
Normal file
78
client/media/CSoundHandler.h
Normal 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;
|
||||||
|
};
|
||||||
658
client/media/CVideoHandler.cpp
Normal file
658
client/media/CVideoHandler.cpp
Normal file
@@ -0,0 +1,658 @@
|
|||||||
|
/*
|
||||||
|
* CVideoHandler.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 "CVideoHandler.h"
|
||||||
|
|
||||||
|
#ifndef DISABLE_VIDEO
|
||||||
|
|
||||||
|
#include "ISoundPlayer.h"
|
||||||
|
|
||||||
|
#include "../CGameInfo.h"
|
||||||
|
#include "../CMT.h"
|
||||||
|
#include "../eventsSDL/InputHandler.h"
|
||||||
|
#include "../gui/CGuiHandler.h"
|
||||||
|
#include "../render/Canvas.h"
|
||||||
|
#include "../renderSDL/SDL_Extensions.h"
|
||||||
|
|
||||||
|
#include "../../lib/filesystem/CInputStream.h"
|
||||||
|
#include "../../lib/filesystem/Filesystem.h"
|
||||||
|
#include "../../lib/CGeneralTextHandler.h"
|
||||||
|
#include "../../lib/Languages.h"
|
||||||
|
|
||||||
|
#include <SDL_render.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavutil/imgutils.h>
|
||||||
|
#include <libswscale/swscale.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a set of functions to read data
|
||||||
|
static int lodRead(void * opaque, uint8_t * buf, int size)
|
||||||
|
{
|
||||||
|
auto * data = static_cast<CInputStream *>(opaque);
|
||||||
|
auto bytesRead = data->read(buf, size);
|
||||||
|
if(bytesRead == 0)
|
||||||
|
return AVERROR_EOF;
|
||||||
|
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
static si64 lodSeek(void * opaque, si64 pos, int whence)
|
||||||
|
{
|
||||||
|
auto * data = static_cast<CInputStream *>(opaque);
|
||||||
|
|
||||||
|
if(whence & AVSEEK_SIZE)
|
||||||
|
return data->getSize();
|
||||||
|
|
||||||
|
return data->seek(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] static void throwFFmpegError(int errorCode)
|
||||||
|
{
|
||||||
|
std::array<char, AV_ERROR_MAX_STRING_SIZE> errorMessage{};
|
||||||
|
av_strerror(errorCode, errorMessage.data(), errorMessage.size());
|
||||||
|
|
||||||
|
throw std::runtime_error(errorMessage.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::unique_ptr<CInputStream> findVideoData(const VideoPath & videoToOpen)
|
||||||
|
{
|
||||||
|
if(CResourceHandler::get()->existsResource(videoToOpen))
|
||||||
|
return CResourceHandler::get()->load(videoToOpen);
|
||||||
|
|
||||||
|
auto highQualityVideoToOpenWithDir = videoToOpen.addPrefix("VIDEO/");
|
||||||
|
auto lowQualityVideo = videoToOpen.toType<EResType::VIDEO_LOW_QUALITY>();
|
||||||
|
auto lowQualityVideoWithDir = highQualityVideoToOpenWithDir.toType<EResType::VIDEO_LOW_QUALITY>();
|
||||||
|
|
||||||
|
if(CResourceHandler::get()->existsResource(highQualityVideoToOpenWithDir))
|
||||||
|
return CResourceHandler::get()->load(highQualityVideoToOpenWithDir);
|
||||||
|
|
||||||
|
if(CResourceHandler::get()->existsResource(lowQualityVideo))
|
||||||
|
return CResourceHandler::get()->load(lowQualityVideo);
|
||||||
|
|
||||||
|
if(CResourceHandler::get()->existsResource(lowQualityVideoWithDir))
|
||||||
|
return CResourceHandler::get()->load(lowQualityVideoWithDir);
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FFMpegStream::openInput(const VideoPath & videoToOpen)
|
||||||
|
{
|
||||||
|
input = findVideoData(videoToOpen);
|
||||||
|
|
||||||
|
return input != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FFMpegStream::openContext()
|
||||||
|
{
|
||||||
|
static const int BUFFER_SIZE = 4096;
|
||||||
|
input->seek(0);
|
||||||
|
|
||||||
|
auto * buffer = static_cast<unsigned char *>(av_malloc(BUFFER_SIZE)); // will be freed by ffmpeg
|
||||||
|
context = avio_alloc_context(buffer, BUFFER_SIZE, 0, input.get(), lodRead, nullptr, lodSeek);
|
||||||
|
|
||||||
|
formatContext = avformat_alloc_context();
|
||||||
|
formatContext->pb = context;
|
||||||
|
// filename is not needed - file was already open and stored in this->data;
|
||||||
|
int avfopen = avformat_open_input(&formatContext, "dummyFilename", nullptr, nullptr);
|
||||||
|
|
||||||
|
if(avfopen != 0)
|
||||||
|
throwFFmpegError(avfopen);
|
||||||
|
|
||||||
|
// Retrieve stream information
|
||||||
|
int findStreamInfo = avformat_find_stream_info(formatContext, nullptr);
|
||||||
|
|
||||||
|
if(avfopen < 0)
|
||||||
|
throwFFmpegError(findStreamInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FFMpegStream::openCodec(int desiredStreamIndex)
|
||||||
|
{
|
||||||
|
streamIndex = desiredStreamIndex;
|
||||||
|
|
||||||
|
// Find the decoder for the stream
|
||||||
|
codec = avcodec_find_decoder(formatContext->streams[streamIndex]->codecpar->codec_id);
|
||||||
|
|
||||||
|
if(codec == nullptr)
|
||||||
|
throw std::runtime_error("Unsupported codec");
|
||||||
|
|
||||||
|
codecContext = avcodec_alloc_context3(codec);
|
||||||
|
if(codecContext == nullptr)
|
||||||
|
throw std::runtime_error("Failed to create codec context");
|
||||||
|
|
||||||
|
// Get a pointer to the codec context for the video stream
|
||||||
|
int ret = avcodec_parameters_to_context(codecContext, formatContext->streams[streamIndex]->codecpar);
|
||||||
|
if(ret < 0)
|
||||||
|
{
|
||||||
|
//We cannot get codec from parameters
|
||||||
|
avcodec_free_context(&codecContext);
|
||||||
|
throwFFmpegError(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open codec
|
||||||
|
ret = avcodec_open2(codecContext, codec, nullptr);
|
||||||
|
if(ret < 0)
|
||||||
|
{
|
||||||
|
// Could not open codec
|
||||||
|
codec = nullptr;
|
||||||
|
throwFFmpegError(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate video frame
|
||||||
|
frame = av_frame_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVCodecParameters * FFMpegStream::getCodecParameters() const
|
||||||
|
{
|
||||||
|
return formatContext->streams[streamIndex]->codecpar;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVCodecContext * FFMpegStream::getCodecContext() const
|
||||||
|
{
|
||||||
|
return codecContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVFrame * FFMpegStream::getCurrentFrame() const
|
||||||
|
{
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CVideoInstance::openVideo()
|
||||||
|
{
|
||||||
|
openContext();
|
||||||
|
openCodec(findVideoStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CVideoInstance::prepareOutput(bool scaleToScreenSize, bool useTextureOutput)
|
||||||
|
{
|
||||||
|
//setup scaling
|
||||||
|
if(scaleToScreenSize)
|
||||||
|
{
|
||||||
|
dimensions.x = screen->w;
|
||||||
|
dimensions.y = screen->h;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dimensions.x = getCodecContext()->width;
|
||||||
|
dimensions.y = getCodecContext()->height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate a place to put our YUV image on that screen
|
||||||
|
if (useTextureOutput)
|
||||||
|
{
|
||||||
|
std::array potentialFormats = {
|
||||||
|
AV_PIX_FMT_YUV420P, // -> SDL_PIXELFORMAT_IYUV - most of H3 videos use YUV format, so it is preferred to save some space & conversion time
|
||||||
|
AV_PIX_FMT_RGB32, // -> SDL_PIXELFORMAT_ARGB8888 - some .smk videos actually use palette, so RGB > YUV. This is also our screen texture format
|
||||||
|
AV_PIX_FMT_NONE
|
||||||
|
};
|
||||||
|
|
||||||
|
auto preferredFormat = avcodec_find_best_pix_fmt_of_list(potentialFormats.data(), getCodecContext()->pix_fmt, false, nullptr);
|
||||||
|
|
||||||
|
if (preferredFormat == AV_PIX_FMT_YUV420P)
|
||||||
|
textureYUV = SDL_CreateTexture( mainRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, dimensions.x, dimensions.y);
|
||||||
|
else
|
||||||
|
textureRGB = SDL_CreateTexture( mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, dimensions.x, dimensions.y);
|
||||||
|
sws = sws_getContext(getCodecContext()->width, getCodecContext()->height, getCodecContext()->pix_fmt,
|
||||||
|
dimensions.x, dimensions.y, preferredFormat,
|
||||||
|
SWS_BICUBIC, nullptr, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
surface = CSDL_Ext::newSurface(dimensions.x, dimensions.y);
|
||||||
|
sws = sws_getContext(getCodecContext()->width, getCodecContext()->height, getCodecContext()->pix_fmt,
|
||||||
|
dimensions.x, dimensions.y, AV_PIX_FMT_RGB32,
|
||||||
|
SWS_BICUBIC, nullptr, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sws == nullptr)
|
||||||
|
throw std::runtime_error("Failed to create sws");
|
||||||
|
}
|
||||||
|
|
||||||
|
void FFMpegStream::decodeNextFrame()
|
||||||
|
{
|
||||||
|
AVPacket packet;
|
||||||
|
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
int rc = avcodec_receive_frame(codecContext, frame);
|
||||||
|
if(rc == AVERROR(EAGAIN))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(rc < 0)
|
||||||
|
throwFFmpegError(rc);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
int ret = av_read_frame(formatContext, &packet);
|
||||||
|
if(ret < 0)
|
||||||
|
{
|
||||||
|
if(ret == AVERROR_EOF)
|
||||||
|
{
|
||||||
|
av_packet_unref(&packet);
|
||||||
|
av_frame_free(&frame);
|
||||||
|
frame = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throwFFmpegError(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this a packet from the video stream?
|
||||||
|
if(packet.stream_index == streamIndex)
|
||||||
|
{
|
||||||
|
// Decode video frame
|
||||||
|
int rc = avcodec_send_packet(codecContext, &packet);
|
||||||
|
if(rc < 0 && rc != AVERROR(EAGAIN))
|
||||||
|
throwFFmpegError(rc);
|
||||||
|
|
||||||
|
rc = avcodec_receive_frame(codecContext, frame);
|
||||||
|
if(rc == AVERROR(EAGAIN))
|
||||||
|
{
|
||||||
|
av_packet_unref(&packet);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(rc < 0)
|
||||||
|
throwFFmpegError(rc);
|
||||||
|
|
||||||
|
av_packet_unref(&packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
av_packet_unref(&packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CVideoInstance::loadNextFrame()
|
||||||
|
{
|
||||||
|
decodeNextFrame();
|
||||||
|
const AVFrame * frame = getCurrentFrame();
|
||||||
|
|
||||||
|
if(!frame)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint8_t * data[4] = {};
|
||||||
|
int linesize[4] = {};
|
||||||
|
|
||||||
|
if(textureYUV)
|
||||||
|
{
|
||||||
|
av_image_alloc(data, linesize, dimensions.x, dimensions.y, AV_PIX_FMT_YUV420P, 1);
|
||||||
|
sws_scale(sws, frame->data, frame->linesize, 0, getCodecContext()->height, data, linesize);
|
||||||
|
SDL_UpdateYUVTexture(textureYUV, nullptr, data[0], linesize[0], data[1], linesize[1], data[2], linesize[2]);
|
||||||
|
av_freep(&data[0]);
|
||||||
|
}
|
||||||
|
if(textureRGB)
|
||||||
|
{
|
||||||
|
av_image_alloc(data, linesize, dimensions.x, dimensions.y, AV_PIX_FMT_RGB32, 1);
|
||||||
|
sws_scale(sws, frame->data, frame->linesize, 0, getCodecContext()->height, data, linesize);
|
||||||
|
SDL_UpdateTexture(textureRGB, nullptr, data[0], linesize[0]);
|
||||||
|
av_freep(&data[0]);
|
||||||
|
}
|
||||||
|
if(surface)
|
||||||
|
{
|
||||||
|
// Avoid buffer overflow caused by sws_scale():
|
||||||
|
// http://trac.ffmpeg.org/ticket/9254
|
||||||
|
|
||||||
|
size_t pic_bytes = surface->pitch * surface->h;
|
||||||
|
size_t ffmped_pad = 1024; /* a few bytes of overflow will go here */
|
||||||
|
void * for_sws = av_malloc(pic_bytes + ffmped_pad);
|
||||||
|
data[0] = (ui8 *)for_sws;
|
||||||
|
linesize[0] = surface->pitch;
|
||||||
|
|
||||||
|
sws_scale(sws, frame->data, frame->linesize, 0, getCodecContext()->height, data, linesize);
|
||||||
|
memcpy(surface->pixels, for_sws, pic_bytes);
|
||||||
|
av_free(for_sws);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CVideoInstance::videoEnded()
|
||||||
|
{
|
||||||
|
return getCurrentFrame() == nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CVideoInstance::~CVideoInstance()
|
||||||
|
{
|
||||||
|
sws_freeContext(sws);
|
||||||
|
SDL_DestroyTexture(textureYUV);
|
||||||
|
SDL_DestroyTexture(textureRGB);
|
||||||
|
SDL_FreeSurface(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
FFMpegStream::~FFMpegStream()
|
||||||
|
{
|
||||||
|
av_frame_free(&frame);
|
||||||
|
|
||||||
|
avcodec_close(codecContext);
|
||||||
|
avcodec_free_context(&codecContext);
|
||||||
|
|
||||||
|
avcodec_close(codecContext);
|
||||||
|
avcodec_free_context(&codecContext);
|
||||||
|
|
||||||
|
avformat_close_input(&formatContext);
|
||||||
|
av_free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
Point CVideoInstance::size()
|
||||||
|
{
|
||||||
|
if(!getCurrentFrame())
|
||||||
|
throw std::runtime_error("Invalid video frame!");
|
||||||
|
|
||||||
|
return Point(getCurrentFrame()->width, getCurrentFrame()->height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CVideoInstance::show(const Point & position, Canvas & canvas)
|
||||||
|
{
|
||||||
|
if(sws == nullptr)
|
||||||
|
throw std::runtime_error("No video to show!");
|
||||||
|
|
||||||
|
CSDL_Ext::blitSurface(surface, canvas.getInternalSurface(), position);
|
||||||
|
}
|
||||||
|
|
||||||
|
double FFMpegStream::getCurrentFrameEndTime() const
|
||||||
|
{
|
||||||
|
#if(LIBAVUTIL_VERSION_MAJOR < 58)
|
||||||
|
auto packet_duration = frame->pkt_duration;
|
||||||
|
#else
|
||||||
|
auto packet_duration = frame->duration;
|
||||||
|
#endif
|
||||||
|
return (frame->pts + packet_duration) * av_q2d(formatContext->streams[streamIndex]->time_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
double FFMpegStream::getCurrentFrameDuration() const
|
||||||
|
{
|
||||||
|
#if(LIBAVUTIL_VERSION_MAJOR < 58)
|
||||||
|
auto packet_duration = frame->pkt_duration;
|
||||||
|
#else
|
||||||
|
auto packet_duration = frame->duration;
|
||||||
|
#endif
|
||||||
|
return packet_duration * av_q2d(formatContext->streams[streamIndex]->time_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CVideoInstance::tick(uint32_t msPassed)
|
||||||
|
{
|
||||||
|
if(sws == nullptr)
|
||||||
|
throw std::runtime_error("No video to show!");
|
||||||
|
|
||||||
|
if(videoEnded())
|
||||||
|
throw std::runtime_error("Video already ended!");
|
||||||
|
|
||||||
|
frameTime += msPassed / 1000.0;
|
||||||
|
|
||||||
|
if(frameTime >= getCurrentFrameEndTime())
|
||||||
|
loadNextFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FFMpegFormatDescription
|
||||||
|
{
|
||||||
|
uint8_t sampleSizeBytes;
|
||||||
|
uint8_t wavFormatID;
|
||||||
|
bool isPlanar;
|
||||||
|
};
|
||||||
|
|
||||||
|
static FFMpegFormatDescription getAudioFormatProperties(int audioFormat)
|
||||||
|
{
|
||||||
|
switch (audioFormat)
|
||||||
|
{
|
||||||
|
case AV_SAMPLE_FMT_U8: return { 1, 1, false};
|
||||||
|
case AV_SAMPLE_FMT_U8P: return { 1, 1, true};
|
||||||
|
case AV_SAMPLE_FMT_S16: return { 2, 1, false};
|
||||||
|
case AV_SAMPLE_FMT_S16P: return { 2, 1, true};
|
||||||
|
case AV_SAMPLE_FMT_S32: return { 4, 1, false};
|
||||||
|
case AV_SAMPLE_FMT_S32P: return { 4, 1, true};
|
||||||
|
case AV_SAMPLE_FMT_S64: return { 8, 1, false};
|
||||||
|
case AV_SAMPLE_FMT_S64P: return { 8, 1, true};
|
||||||
|
case AV_SAMPLE_FMT_FLT: return { 4, 3, false};
|
||||||
|
case AV_SAMPLE_FMT_FLTP: return { 4, 3, true};
|
||||||
|
case AV_SAMPLE_FMT_DBL: return { 8, 3, false};
|
||||||
|
case AV_SAMPLE_FMT_DBLP: return { 8, 3, true};
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Invalid audio format");
|
||||||
|
}
|
||||||
|
|
||||||
|
int FFMpegStream::findAudioStream() const
|
||||||
|
{
|
||||||
|
std::vector<int> audioStreamIndices;
|
||||||
|
|
||||||
|
for(int i = 0; i < formatContext->nb_streams; i++)
|
||||||
|
if(formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||||
|
audioStreamIndices.push_back(i);
|
||||||
|
|
||||||
|
if (audioStreamIndices.empty())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (audioStreamIndices.size() == 1)
|
||||||
|
return audioStreamIndices.front();
|
||||||
|
|
||||||
|
// multiple audio streams - try to pick best one based on language settings
|
||||||
|
std::map<int, std::string> streamToLanguage;
|
||||||
|
|
||||||
|
// Approach 1 - check if stream has language set in metadata
|
||||||
|
for (auto const & index : audioStreamIndices)
|
||||||
|
{
|
||||||
|
const AVDictionaryEntry *e = av_dict_get(formatContext->streams[index]->metadata, "language", nullptr, 0);
|
||||||
|
if (e)
|
||||||
|
streamToLanguage[index] = e->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approach 2 - no metadata found. This may be video from Chronicles which have predefined (presumably hardcoded) list of languages
|
||||||
|
if (streamToLanguage.empty())
|
||||||
|
{
|
||||||
|
if (audioStreamIndices.size() == 2)
|
||||||
|
{
|
||||||
|
streamToLanguage[audioStreamIndices[0]] = Languages::getLanguageOptions(Languages::ELanguages::ENGLISH).tagISO2;
|
||||||
|
streamToLanguage[audioStreamIndices[1]] = Languages::getLanguageOptions(Languages::ELanguages::GERMAN).tagISO2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioStreamIndices.size() == 5)
|
||||||
|
{
|
||||||
|
streamToLanguage[audioStreamIndices[0]] = Languages::getLanguageOptions(Languages::ELanguages::ENGLISH).tagISO2;
|
||||||
|
streamToLanguage[audioStreamIndices[1]] = Languages::getLanguageOptions(Languages::ELanguages::FRENCH).tagISO2;
|
||||||
|
streamToLanguage[audioStreamIndices[2]] = Languages::getLanguageOptions(Languages::ELanguages::GERMAN).tagISO2;
|
||||||
|
streamToLanguage[audioStreamIndices[3]] = Languages::getLanguageOptions(Languages::ELanguages::ITALIAN).tagISO2;
|
||||||
|
streamToLanguage[audioStreamIndices[4]] = Languages::getLanguageOptions(Languages::ELanguages::SPANISH).tagISO2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string preferredLanguageName = CGI->generaltexth->getPreferredLanguage();
|
||||||
|
std::string preferredTag = Languages::getLanguageOptions(preferredLanguageName).tagISO2;
|
||||||
|
|
||||||
|
for (auto const & entry : streamToLanguage)
|
||||||
|
if (entry.second == preferredTag)
|
||||||
|
return entry.first;
|
||||||
|
|
||||||
|
return audioStreamIndices.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
int FFMpegStream::findVideoStream() const
|
||||||
|
{
|
||||||
|
for(int i = 0; i < formatContext->nb_streams; i++)
|
||||||
|
if(formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
|
||||||
|
return i;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::unique_ptr<ui8 []>, si64> CAudioInstance::extractAudio(const VideoPath & videoToOpen)
|
||||||
|
{
|
||||||
|
if (!openInput(videoToOpen))
|
||||||
|
return { nullptr, 0};
|
||||||
|
openContext();
|
||||||
|
openCodec(findAudioStream());
|
||||||
|
|
||||||
|
const auto * codecpar = getCodecParameters();
|
||||||
|
|
||||||
|
std::vector<ui8> samples;
|
||||||
|
|
||||||
|
auto formatProperties = getAudioFormatProperties(codecpar->format);
|
||||||
|
#if(LIBAVUTIL_VERSION_MAJOR < 58)
|
||||||
|
int numChannels = codecpar->channels;
|
||||||
|
#else
|
||||||
|
int numChannels = codecpar->ch_layout.nb_channels;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
samples.reserve(44100 * 5); // arbitrary 5-second buffer
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
decodeNextFrame();
|
||||||
|
const AVFrame * frame = getCurrentFrame();
|
||||||
|
|
||||||
|
if (!frame)
|
||||||
|
break;
|
||||||
|
|
||||||
|
int samplesToRead = frame->nb_samples * numChannels;
|
||||||
|
int bytesToRead = samplesToRead * formatProperties.sampleSizeBytes;
|
||||||
|
|
||||||
|
if (formatProperties.isPlanar && numChannels > 1)
|
||||||
|
{
|
||||||
|
// Workaround for lack of resampler
|
||||||
|
// Currently, ffmpeg on conan systems is built without sws resampler
|
||||||
|
// Because of that, and because wav format does not supports 'planar' formats from ffmpeg
|
||||||
|
// we need to de-planarize it and convert to "normal" (non-planar / interleaved) stream
|
||||||
|
samples.reserve(samples.size() + bytesToRead);
|
||||||
|
for (int sm = 0; sm < frame->nb_samples; ++sm)
|
||||||
|
for (int ch = 0; ch < numChannels; ++ch)
|
||||||
|
samples.insert(samples.end(), frame->data[ch] + sm * formatProperties.sampleSizeBytes, frame->data[ch] + (sm+1) * formatProperties.sampleSizeBytes );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
samples.insert(samples.end(), frame->data[0], frame->data[0] + bytesToRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WavHeader {
|
||||||
|
ui8 RIFF[4] = {'R', 'I', 'F', 'F'};
|
||||||
|
ui32 ChunkSize;
|
||||||
|
ui8 WAVE[4] = {'W', 'A', 'V', 'E'};
|
||||||
|
ui8 fmt[4] = {'f', 'm', 't', ' '};
|
||||||
|
ui32 Subchunk1Size = 16;
|
||||||
|
ui16 AudioFormat = 1;
|
||||||
|
ui16 NumOfChan = 2;
|
||||||
|
ui32 SamplesPerSec = 22050;
|
||||||
|
ui32 bytesPerSec = 22050 * 2;
|
||||||
|
ui16 blockAlign = 2;
|
||||||
|
ui16 bitsPerSample = 32;
|
||||||
|
ui8 Subchunk2ID[4] = {'d', 'a', 't', 'a'};
|
||||||
|
ui32 Subchunk2Size;
|
||||||
|
};
|
||||||
|
|
||||||
|
WavHeader wav;
|
||||||
|
wav.ChunkSize = samples.size() + sizeof(WavHeader) - 8;
|
||||||
|
wav.AudioFormat = formatProperties.wavFormatID; // 1 = PCM, 3 = IEEE float
|
||||||
|
wav.NumOfChan = numChannels;
|
||||||
|
wav.SamplesPerSec = codecpar->sample_rate;
|
||||||
|
wav.bytesPerSec = codecpar->sample_rate * formatProperties.sampleSizeBytes;
|
||||||
|
wav.bitsPerSample = formatProperties.sampleSizeBytes * 8;
|
||||||
|
wav.Subchunk2Size = samples.size() + sizeof(WavHeader) - 44;
|
||||||
|
auto * wavPtr = reinterpret_cast<ui8*>(&wav);
|
||||||
|
|
||||||
|
auto dat = std::make_pair(std::make_unique<ui8[]>(samples.size() + sizeof(WavHeader)), samples.size() + sizeof(WavHeader));
|
||||||
|
std::copy(wavPtr, wavPtr + sizeof(WavHeader), dat.first.get());
|
||||||
|
std::copy(samples.begin(), samples.end(), dat.first.get() + sizeof(WavHeader));
|
||||||
|
|
||||||
|
return dat;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & position, bool useOverlay, bool scale, bool stopOnKey)
|
||||||
|
{
|
||||||
|
CVideoInstance instance;
|
||||||
|
CAudioInstance audio;
|
||||||
|
|
||||||
|
auto extractedAudio = audio.extractAudio(name);
|
||||||
|
int audioHandle = CCS->soundh->playSound(extractedAudio);
|
||||||
|
|
||||||
|
if (!instance.openInput(name))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
instance.openVideo();
|
||||||
|
instance.prepareOutput(scale, useOverlay);
|
||||||
|
|
||||||
|
auto lastTimePoint = boost::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
while(instance.loadNextFrame())
|
||||||
|
{
|
||||||
|
if(stopOnKey)
|
||||||
|
{
|
||||||
|
GH.input().fetchEvents();
|
||||||
|
if(GH.input().ignoreEventsUntilInput())
|
||||||
|
{
|
||||||
|
CCS->soundh->stopSound(audioHandle);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Rect rect;
|
||||||
|
rect.x = position.x;
|
||||||
|
rect.y = position.y;
|
||||||
|
rect.w = instance.dimensions.x;
|
||||||
|
rect.h = instance.dimensions.y;
|
||||||
|
|
||||||
|
if(useOverlay)
|
||||||
|
SDL_RenderFillRect(mainRenderer, &rect);
|
||||||
|
else
|
||||||
|
SDL_RenderClear(mainRenderer);
|
||||||
|
|
||||||
|
if(instance.textureYUV)
|
||||||
|
SDL_RenderCopy(mainRenderer, instance.textureYUV, nullptr, &rect);
|
||||||
|
else
|
||||||
|
SDL_RenderCopy(mainRenderer, instance.textureRGB, nullptr, &rect);
|
||||||
|
|
||||||
|
SDL_RenderPresent(mainRenderer);
|
||||||
|
|
||||||
|
// Framerate delay
|
||||||
|
double targetFrameTimeSeconds = instance.getCurrentFrameDuration();
|
||||||
|
auto targetFrameTime = boost::chrono::milliseconds(static_cast<int>(1000 * targetFrameTimeSeconds));
|
||||||
|
|
||||||
|
auto timePointAfterPresent = boost::chrono::steady_clock::now();
|
||||||
|
auto timeSpentBusy = boost::chrono::duration_cast<boost::chrono::milliseconds>(timePointAfterPresent - lastTimePoint);
|
||||||
|
|
||||||
|
logGlobal->info("Sleeping for %d", (targetFrameTime - timeSpentBusy).count());
|
||||||
|
if(targetFrameTime > timeSpentBusy)
|
||||||
|
boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy);
|
||||||
|
|
||||||
|
lastTimePoint = boost::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CVideoPlayer::playIntroVideo(const VideoPath & name)
|
||||||
|
{
|
||||||
|
return openAndPlayVideoImpl(name, Point(0, 0), true, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CVideoPlayer::playSpellbookAnimation(const VideoPath & name, const Point & position)
|
||||||
|
{
|
||||||
|
openAndPlayVideoImpl(name, position, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<IVideoInstance> CVideoPlayer::open(const VideoPath & name, bool scaleToScreen)
|
||||||
|
{
|
||||||
|
auto result = std::make_unique<CVideoInstance>();
|
||||||
|
|
||||||
|
if (!result->openInput(name))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
result->openVideo();
|
||||||
|
result->prepareOutput(scaleToScreen, false);
|
||||||
|
result->loadNextFrame(); // prepare 1st frame
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::unique_ptr<ui8[]>, si64> CVideoPlayer::getAudio(const VideoPath & videoToOpen)
|
||||||
|
{
|
||||||
|
CAudioInstance audio;
|
||||||
|
return audio.extractAudio(videoToOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
110
client/media/CVideoHandler.h
Normal file
110
client/media/CVideoHandler.h
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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 "../lib/Point.h"
|
||||||
|
#include "IVideoPlayer.h"
|
||||||
|
|
||||||
|
struct SDL_Surface;
|
||||||
|
struct SDL_Texture;
|
||||||
|
struct AVFormatContext;
|
||||||
|
struct AVCodecContext;
|
||||||
|
struct AVCodecParameters;
|
||||||
|
struct AVCodec;
|
||||||
|
struct AVFrame;
|
||||||
|
struct AVIOContext;
|
||||||
|
|
||||||
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
class CInputStream;
|
||||||
|
class Point;
|
||||||
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|
||||||
|
class FFMpegStream : boost::noncopyable
|
||||||
|
{
|
||||||
|
std::unique_ptr<CInputStream> input;
|
||||||
|
|
||||||
|
AVIOContext * context = nullptr;
|
||||||
|
AVFormatContext * formatContext = nullptr;
|
||||||
|
|
||||||
|
const AVCodec * codec = nullptr;
|
||||||
|
AVCodecContext * codecContext = nullptr;
|
||||||
|
int streamIndex = -1;
|
||||||
|
|
||||||
|
AVFrame * frame = nullptr;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void openContext();
|
||||||
|
void openCodec(int streamIndex);
|
||||||
|
|
||||||
|
int findVideoStream() const;
|
||||||
|
int findAudioStream() const;
|
||||||
|
|
||||||
|
const AVCodecParameters * getCodecParameters() const;
|
||||||
|
const AVCodecContext * getCodecContext() const;
|
||||||
|
void decodeNextFrame();
|
||||||
|
const AVFrame * getCurrentFrame() const;
|
||||||
|
double getCurrentFrameEndTime() const;
|
||||||
|
double getCurrentFrameDuration() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~FFMpegStream();
|
||||||
|
|
||||||
|
bool openInput(const VideoPath & fname);
|
||||||
|
};
|
||||||
|
|
||||||
|
class CAudioInstance final : public FFMpegStream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::pair<std::unique_ptr<ui8[]>, si64> extractAudio(const VideoPath & videoToOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
class CVideoInstance final : public IVideoInstance, public FFMpegStream
|
||||||
|
{
|
||||||
|
friend class CVideoPlayer;
|
||||||
|
|
||||||
|
struct SwsContext * sws = nullptr;
|
||||||
|
SDL_Texture * textureRGB = nullptr;
|
||||||
|
SDL_Texture * textureYUV = nullptr;
|
||||||
|
SDL_Surface * surface = nullptr;
|
||||||
|
Point dimensions;
|
||||||
|
|
||||||
|
/// video playback current progress, in seconds
|
||||||
|
double frameTime = 0.0;
|
||||||
|
|
||||||
|
void prepareOutput(bool scaleToScreenSize, bool useTextureOutput);
|
||||||
|
|
||||||
|
public:
|
||||||
|
~CVideoInstance();
|
||||||
|
|
||||||
|
void openVideo();
|
||||||
|
bool loadNextFrame();
|
||||||
|
|
||||||
|
bool videoEnded() final;
|
||||||
|
Point size() final;
|
||||||
|
|
||||||
|
void show(const Point & position, Canvas & canvas) final;
|
||||||
|
void tick(uint32_t msPassed) final;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CVideoPlayer final : public IVideoPlayer
|
||||||
|
{
|
||||||
|
bool openAndPlayVideoImpl(const VideoPath & name, const Point & position, bool useOverlay, bool scale, bool stopOnKey);
|
||||||
|
void openVideoFile(CVideoInstance & state, const VideoPath & fname);
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool playIntroVideo(const VideoPath & name) final;
|
||||||
|
void playSpellbookAnimation(const VideoPath & name, const Point & position) final;
|
||||||
|
std::unique_ptr<IVideoInstance> open(const VideoPath & name, bool scaleToScreen) final;
|
||||||
|
std::pair<std::unique_ptr<ui8[]>, si64> getAudio(const VideoPath & videoToOpen) final;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
33
client/media/IMusicPlayer.h
Normal file
33
client/media/IMusicPlayer.h
Normal 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;
|
||||||
|
};
|
||||||
35
client/media/ISoundPlayer.h
Normal file
35
client/media/ISoundPlayer.h
Normal 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;
|
||||||
|
};
|
||||||
54
client/media/IVideoPlayer.h
Normal file
54
client/media/IVideoPlayer.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
class Canvas;
|
||||||
|
|
||||||
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
class Point;
|
||||||
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
|
||||||
|
class IVideoInstance
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Returns true if video playback is over
|
||||||
|
virtual bool videoEnded() = 0;
|
||||||
|
|
||||||
|
/// Returns dimensions of the video
|
||||||
|
virtual Point size() = 0;
|
||||||
|
|
||||||
|
/// Displays current frame at specified position
|
||||||
|
virtual void show(const Point & position, Canvas & canvas) = 0;
|
||||||
|
|
||||||
|
/// Advances video playback by specified duration
|
||||||
|
virtual void tick(uint32_t msPassed) = 0;
|
||||||
|
|
||||||
|
virtual ~IVideoInstance() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IVideoPlayer : boost::noncopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Plays video on top of the screen, returns only after playback is over, aborts on input event
|
||||||
|
virtual bool playIntroVideo(const VideoPath & name) = 0;
|
||||||
|
|
||||||
|
/// Plays video on top of the screen, returns only after playback is over
|
||||||
|
virtual void playSpellbookAnimation(const VideoPath & name, const Point & position) = 0;
|
||||||
|
|
||||||
|
/// Load video from specified path. Returns nullptr on failure
|
||||||
|
virtual std::unique_ptr<IVideoInstance> open(const VideoPath & name, bool scaleToScreen) = 0;
|
||||||
|
|
||||||
|
/// Extracts audio data from provided video in wav format
|
||||||
|
virtual std::pair<std::unique_ptr<ui8[]>, si64> getAudio(const VideoPath & videoToOpen) = 0;
|
||||||
|
|
||||||
|
virtual ~IVideoPlayer() = default;
|
||||||
|
};
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
#include "Images.h"
|
#include "Images.h"
|
||||||
#include "TextControls.h"
|
#include "TextControls.h"
|
||||||
|
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../battle/BattleInterface.h"
|
#include "../battle/BattleInterface.h"
|
||||||
@@ -23,6 +22,7 @@
|
|||||||
#include "../gui/MouseButton.h"
|
#include "../gui/MouseButton.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
#include "../gui/InterfaceObjectConfigurable.h"
|
#include "../gui/InterfaceObjectConfigurable.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../windows/InfoWindows.h"
|
#include "../windows/InfoWindows.h"
|
||||||
#include "../render/CAnimation.h"
|
#include "../render/CAnimation.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
|
|
||||||
#include "../../CCallback.h"
|
#include "../../CCallback.h"
|
||||||
|
|
||||||
|
|||||||
148
client/widgets/VideoWidget.cpp
Normal file
148
client/widgets/VideoWidget.cpp
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* TextControls.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 "VideoWidget.h"
|
||||||
|
|
||||||
|
#include "../CGameInfo.h"
|
||||||
|
#include "../gui/CGuiHandler.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
|
#include "../media/IVideoPlayer.h"
|
||||||
|
#include "../render/Canvas.h"
|
||||||
|
|
||||||
|
VideoWidgetBase::VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio)
|
||||||
|
: playAudio(playAudio)
|
||||||
|
{
|
||||||
|
addUsedEvents(TIME);
|
||||||
|
pos += position;
|
||||||
|
playVideo(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoWidgetBase::~VideoWidgetBase() = default;
|
||||||
|
|
||||||
|
void VideoWidgetBase::playVideo(const VideoPath & fileToPlay)
|
||||||
|
{
|
||||||
|
videoInstance = CCS->videoh->open(fileToPlay, false);
|
||||||
|
if (videoInstance)
|
||||||
|
{
|
||||||
|
pos.w = videoInstance->size().x;
|
||||||
|
pos.h = videoInstance->size().y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playAudio)
|
||||||
|
{
|
||||||
|
loadAudio(fileToPlay);
|
||||||
|
if (isActive())
|
||||||
|
startAudio();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoWidgetBase::show(Canvas & to)
|
||||||
|
{
|
||||||
|
if(videoInstance)
|
||||||
|
videoInstance->show(pos.topLeft(), to);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoWidgetBase::loadAudio(const VideoPath & fileToPlay)
|
||||||
|
{
|
||||||
|
if (!playAudio)
|
||||||
|
return;
|
||||||
|
|
||||||
|
audioData = CCS->videoh->getAudio(fileToPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoWidgetBase::startAudio()
|
||||||
|
{
|
||||||
|
if(audioData.first == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
audioHandle = CCS->soundh->playSound(audioData);
|
||||||
|
|
||||||
|
if(audioHandle != -1)
|
||||||
|
{
|
||||||
|
CCS->soundh->setCallback(
|
||||||
|
audioHandle,
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
this->audioHandle = -1;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoWidgetBase::stopAudio()
|
||||||
|
{
|
||||||
|
if(audioHandle != -1)
|
||||||
|
{
|
||||||
|
CCS->soundh->resetCallback(audioHandle);
|
||||||
|
CCS->soundh->stopSound(audioHandle);
|
||||||
|
audioHandle = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoWidgetBase::activate()
|
||||||
|
{
|
||||||
|
CIntObject::activate();
|
||||||
|
startAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoWidgetBase::deactivate()
|
||||||
|
{
|
||||||
|
CIntObject::deactivate();
|
||||||
|
stopAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoWidgetBase::showAll(Canvas & to)
|
||||||
|
{
|
||||||
|
if(videoInstance)
|
||||||
|
videoInstance->show(pos.topLeft(), to);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoWidgetBase::tick(uint32_t msPassed)
|
||||||
|
{
|
||||||
|
if(videoInstance)
|
||||||
|
{
|
||||||
|
videoInstance->tick(msPassed);
|
||||||
|
|
||||||
|
if(videoInstance->videoEnded())
|
||||||
|
{
|
||||||
|
videoInstance.reset();
|
||||||
|
stopAudio();
|
||||||
|
onPlaybackFinished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoWidget::VideoWidget(const Point & position, const VideoPath & prologue, const VideoPath & looped, bool playAudio)
|
||||||
|
: VideoWidgetBase(position, prologue, playAudio)
|
||||||
|
, loopedVideo(looped)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoWidget::VideoWidget(const Point & position, const VideoPath & looped, bool playAudio)
|
||||||
|
: VideoWidgetBase(position, looped, playAudio)
|
||||||
|
, loopedVideo(looped)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoWidget::onPlaybackFinished()
|
||||||
|
{
|
||||||
|
playVideo(loopedVideo);
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoWidgetOnce::VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, const std::function<void()> & callback)
|
||||||
|
: VideoWidgetBase(position, video, playAudio)
|
||||||
|
, callback(callback)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoWidgetOnce::onPlaybackFinished()
|
||||||
|
{
|
||||||
|
callback();
|
||||||
|
}
|
||||||
65
client/widgets/VideoWidget.h
Normal file
65
client/widgets/VideoWidget.h
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* VideoWidget.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 "../gui/CIntObject.h"
|
||||||
|
|
||||||
|
#include "../lib/filesystem/ResourcePath.h"
|
||||||
|
|
||||||
|
class IVideoInstance;
|
||||||
|
|
||||||
|
class VideoWidgetBase : public CIntObject
|
||||||
|
{
|
||||||
|
std::unique_ptr<IVideoInstance> videoInstance;
|
||||||
|
|
||||||
|
std::pair<std::unique_ptr<ui8[]>, si64> audioData = {nullptr, 0};
|
||||||
|
int audioHandle = -1;
|
||||||
|
bool playAudio = false;
|
||||||
|
|
||||||
|
void loadAudio(const VideoPath & file);
|
||||||
|
void startAudio();
|
||||||
|
void stopAudio();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio);
|
||||||
|
|
||||||
|
virtual void onPlaybackFinished() = 0;
|
||||||
|
void playVideo(const VideoPath & video);
|
||||||
|
|
||||||
|
public:
|
||||||
|
~VideoWidgetBase();
|
||||||
|
|
||||||
|
void activate() override;
|
||||||
|
void deactivate() override;
|
||||||
|
void show(Canvas & to) override;
|
||||||
|
void showAll(Canvas & to) override;
|
||||||
|
void tick(uint32_t msPassed) override;
|
||||||
|
|
||||||
|
void setPlaybackFinishedCallback(std::function<void()>);
|
||||||
|
};
|
||||||
|
|
||||||
|
class VideoWidget final: public VideoWidgetBase
|
||||||
|
{
|
||||||
|
VideoPath loopedVideo;
|
||||||
|
|
||||||
|
void onPlaybackFinished() final;
|
||||||
|
public:
|
||||||
|
VideoWidget(const Point & position, const VideoPath & prologue, const VideoPath & looped, bool playAudio);
|
||||||
|
VideoWidget(const Point & position, const VideoPath & looped, bool playAudio);
|
||||||
|
};
|
||||||
|
|
||||||
|
class VideoWidgetOnce final: public VideoWidgetBase
|
||||||
|
{
|
||||||
|
std::function<void()> callback;
|
||||||
|
|
||||||
|
void onPlaybackFinished() final;
|
||||||
|
public:
|
||||||
|
VideoWidgetOnce(const Point & position, const VideoPath & video, bool playAudio, const std::function<void()> & callback);
|
||||||
|
};
|
||||||
@@ -18,12 +18,12 @@
|
|||||||
#include "CCreatureWindow.h"
|
#include "CCreatureWindow.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../PlayerLocalState.h"
|
#include "../PlayerLocalState.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
|
#include "../media/IMusicPlayer.h"
|
||||||
#include "../widgets/MiscWidgets.h"
|
#include "../widgets/MiscWidgets.h"
|
||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
#include "../widgets/CGarrisonInt.h"
|
#include "../widgets/CGarrisonInt.h"
|
||||||
@@ -43,6 +43,8 @@
|
|||||||
#include "../../CCallback.h"
|
#include "../../CCallback.h"
|
||||||
#include "../../lib/CArtHandler.h"
|
#include "../../lib/CArtHandler.h"
|
||||||
#include "../../lib/CBuildingHandler.h"
|
#include "../../lib/CBuildingHandler.h"
|
||||||
|
#include "../../lib/CConfigHandler.h"
|
||||||
|
#include "../../lib/CSoundBase.h"
|
||||||
#include "../../lib/CCreatureHandler.h"
|
#include "../../lib/CCreatureHandler.h"
|
||||||
#include "../../lib/CGeneralTextHandler.h"
|
#include "../../lib/CGeneralTextHandler.h"
|
||||||
#include "../../lib/GameSettings.h"
|
#include "../../lib/GameSettings.h"
|
||||||
|
|||||||
@@ -11,13 +11,13 @@
|
|||||||
#include "CPuzzleWindow.h"
|
#include "CPuzzleWindow.h"
|
||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../adventureMap/CResDataBar.h"
|
#include "../adventureMap/CResDataBar.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/TextAlignment.h"
|
#include "../gui/TextAlignment.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
#include "../mapView/MapView.h"
|
#include "../mapView/MapView.h"
|
||||||
|
#include "../media/ISoundPlayer.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/Images.h"
|
#include "../widgets/Images.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
|
|||||||
@@ -19,12 +19,12 @@
|
|||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../PlayerLocalState.h"
|
#include "../PlayerLocalState.h"
|
||||||
#include "../CVideoHandler.h"
|
|
||||||
|
|
||||||
#include "../battle/BattleInterface.h"
|
#include "../battle/BattleInterface.h"
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
#include "../gui/WindowHandler.h"
|
#include "../gui/WindowHandler.h"
|
||||||
|
#include "../media/IVideoPlayer.h"
|
||||||
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
#include "../widgets/GraphicalPrimitiveCanvas.h"
|
||||||
#include "../widgets/CComponent.h"
|
#include "../widgets/CComponent.h"
|
||||||
#include "../widgets/CTextInput.h"
|
#include "../widgets/CTextInput.h"
|
||||||
@@ -524,13 +524,13 @@ void CSpellWindow::setCurrentPage(int value)
|
|||||||
void CSpellWindow::turnPageLeft()
|
void CSpellWindow::turnPageLeft()
|
||||||
{
|
{
|
||||||
if(settings["video"]["spellbookAnimation"].Bool() && !isBigSpellbook)
|
if(settings["video"]["spellbookAnimation"].Bool() && !isBigSpellbook)
|
||||||
CCS->videoh->openAndPlayVideo(VideoPath::builtin("PGTRNLFT.SMK"), pos.x+13, pos.y+15, EVideoType::SPELLBOOK);
|
CCS->videoh->playSpellbookAnimation(VideoPath::builtin("PGTRNLFT.SMK"), pos.topLeft() + Point(13, 15));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSpellWindow::turnPageRight()
|
void CSpellWindow::turnPageRight()
|
||||||
{
|
{
|
||||||
if(settings["video"]["spellbookAnimation"].Bool() && !isBigSpellbook)
|
if(settings["video"]["spellbookAnimation"].Bool() && !isBigSpellbook)
|
||||||
CCS->videoh->openAndPlayVideo(VideoPath::builtin("PGTRNRGH.SMK"), pos.x+13, pos.y+15, EVideoType::SPELLBOOK);
|
CCS->videoh->playSpellbookAnimation(VideoPath::builtin("PGTRNRGH.SMK"), pos.topLeft() + Point(13, 15));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSpellWindow::keyPressed(EShortcut key)
|
void CSpellWindow::keyPressed(EShortcut key)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
#include "../../lib/CGeneralTextHandler.h"
|
#include "../../lib/CGeneralTextHandler.h"
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CVideoHandler.h"
|
|
||||||
|
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/Shortcut.h"
|
#include "../gui/Shortcut.h"
|
||||||
@@ -24,6 +23,7 @@
|
|||||||
#include "../widgets/Images.h"
|
#include "../widgets/Images.h"
|
||||||
#include "../widgets/Buttons.h"
|
#include "../widgets/Buttons.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
|
#include "../widgets/VideoWidget.h"
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
|
|
||||||
CTutorialWindow::CTutorialWindow(const TutorialMode & m)
|
CTutorialWindow::CTutorialWindow(const TutorialMode & m)
|
||||||
@@ -54,7 +54,10 @@ CTutorialWindow::CTutorialWindow(const TutorialMode & m)
|
|||||||
|
|
||||||
void CTutorialWindow::setContent()
|
void CTutorialWindow::setContent()
|
||||||
{
|
{
|
||||||
video = "tutorial/" + videos[page];
|
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||||
|
auto video = VideoPath::builtin("tutorial/" + videos[page]);
|
||||||
|
|
||||||
|
videoPlayer = std::make_shared<VideoWidget>(Point(30, 120), video, false);
|
||||||
|
|
||||||
buttonLeft->block(page<1);
|
buttonLeft->block(page<1);
|
||||||
buttonRight->block(page>videos.size() - 2);
|
buttonRight->block(page>videos.size() - 2);
|
||||||
@@ -98,26 +101,3 @@ void CTutorialWindow::previous()
|
|||||||
deactivate();
|
deactivate();
|
||||||
activate();
|
activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CTutorialWindow::show(Canvas & to)
|
|
||||||
{
|
|
||||||
CCS->videoh->update(pos.x + 30, pos.y + 120, to.getInternalSurface(), true, false,
|
|
||||||
[&]()
|
|
||||||
{
|
|
||||||
CCS->videoh->close();
|
|
||||||
CCS->videoh->open(VideoPath::builtin(video));
|
|
||||||
});
|
|
||||||
|
|
||||||
CIntObject::show(to);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTutorialWindow::activate()
|
|
||||||
{
|
|
||||||
CCS->videoh->open(VideoPath::builtin(video));
|
|
||||||
CIntObject::activate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTutorialWindow::deactivate()
|
|
||||||
{
|
|
||||||
CCS->videoh->close();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class CFilledTexture;
|
|||||||
class CButton;
|
class CButton;
|
||||||
class CLabel;
|
class CLabel;
|
||||||
class CMultiLineLabel;
|
class CMultiLineLabel;
|
||||||
|
class VideoWidget;
|
||||||
|
|
||||||
enum TutorialMode
|
enum TutorialMode
|
||||||
{
|
{
|
||||||
@@ -33,8 +34,8 @@ class CTutorialWindow : public CWindowObject
|
|||||||
|
|
||||||
std::shared_ptr<CLabel> labelTitle;
|
std::shared_ptr<CLabel> labelTitle;
|
||||||
std::shared_ptr<CMultiLineLabel> labelInformation;
|
std::shared_ptr<CMultiLineLabel> labelInformation;
|
||||||
|
std::shared_ptr<VideoWidget> videoPlayer;
|
||||||
|
|
||||||
std::string video;
|
|
||||||
std::vector<std::string> videos;
|
std::vector<std::string> videos;
|
||||||
|
|
||||||
int page;
|
int page;
|
||||||
@@ -47,8 +48,4 @@ class CTutorialWindow : public CWindowObject
|
|||||||
public:
|
public:
|
||||||
CTutorialWindow(const TutorialMode & m);
|
CTutorialWindow(const TutorialMode & m);
|
||||||
static void openWindowFirstTime(const TutorialMode & m);
|
static void openWindowFirstTime(const TutorialMode & m);
|
||||||
|
|
||||||
void show(Canvas & to) override;
|
|
||||||
void activate() override;
|
|
||||||
void deactivate() override;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
|
|
||||||
#include "../../CCallback.h"
|
#include "../../CCallback.h"
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,7 @@
|
|||||||
#include "../CGameInfo.h"
|
#include "../CGameInfo.h"
|
||||||
#include "../CServerHandler.h"
|
#include "../CServerHandler.h"
|
||||||
#include "../Client.h"
|
#include "../Client.h"
|
||||||
#include "../CMusicHandler.h"
|
|
||||||
#include "../CPlayerInterface.h"
|
#include "../CPlayerInterface.h"
|
||||||
#include "../CVideoHandler.h"
|
|
||||||
|
|
||||||
#include "../gui/CGuiHandler.h"
|
#include "../gui/CGuiHandler.h"
|
||||||
#include "../gui/CursorHandler.h"
|
#include "../gui/CursorHandler.h"
|
||||||
@@ -36,6 +34,7 @@
|
|||||||
#include "../widgets/Slider.h"
|
#include "../widgets/Slider.h"
|
||||||
#include "../widgets/TextControls.h"
|
#include "../widgets/TextControls.h"
|
||||||
#include "../widgets/ObjectLists.h"
|
#include "../widgets/ObjectLists.h"
|
||||||
|
#include "../widgets/VideoWidget.h"
|
||||||
|
|
||||||
#include "../render/Canvas.h"
|
#include "../render/Canvas.h"
|
||||||
#include "../render/CAnimation.h"
|
#include "../render/CAnimation.h"
|
||||||
@@ -58,6 +57,7 @@
|
|||||||
#include "../lib/GameSettings.h"
|
#include "../lib/GameSettings.h"
|
||||||
#include "../lib/CondSh.h"
|
#include "../lib/CondSh.h"
|
||||||
#include "../lib/CSkillHandler.h"
|
#include "../lib/CSkillHandler.h"
|
||||||
|
#include "../lib/CSoundBase.h"
|
||||||
#include "../lib/filesystem/Filesystem.h"
|
#include "../lib/filesystem/Filesystem.h"
|
||||||
#include "../lib/TextOperations.h"
|
#include "../lib/TextOperations.h"
|
||||||
|
|
||||||
@@ -515,11 +515,11 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
|
|||||||
recruit->block(true);
|
recruit->block(true);
|
||||||
}
|
}
|
||||||
if(LOCPLINT->castleInt)
|
if(LOCPLINT->castleInt)
|
||||||
CCS->videoh->open(LOCPLINT->castleInt->town->town->clientInfo.tavernVideo);
|
videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), LOCPLINT->castleInt->town->town->clientInfo.tavernVideo, false);
|
||||||
else if(const auto * townObj = dynamic_cast<const CGTownInstance *>(TavernObj))
|
else if(const auto * townObj = dynamic_cast<const CGTownInstance *>(TavernObj))
|
||||||
CCS->videoh->open(townObj->town->clientInfo.tavernVideo);
|
videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), townObj->town->clientInfo.tavernVideo, false);
|
||||||
else
|
else
|
||||||
CCS->videoh->open(VideoPath::builtin("TAVERN.BIK"));
|
videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), VideoPath::builtin("TAVERN.BIK"), false);
|
||||||
|
|
||||||
addInvite();
|
addInvite();
|
||||||
}
|
}
|
||||||
@@ -572,11 +572,6 @@ void CTavernWindow::close()
|
|||||||
CStatusbarWindow::close();
|
CStatusbarWindow::close();
|
||||||
}
|
}
|
||||||
|
|
||||||
CTavernWindow::~CTavernWindow()
|
|
||||||
{
|
|
||||||
CCS->videoh->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTavernWindow::show(Canvas & to)
|
void CTavernWindow::show(Canvas & to)
|
||||||
{
|
{
|
||||||
CWindowObject::show(to);
|
CWindowObject::show(to);
|
||||||
@@ -600,8 +595,6 @@ void CTavernWindow::show(Canvas & to)
|
|||||||
|
|
||||||
to.drawBorder(Rect::createAround(sel->pos, 2), Colors::BRIGHT_YELLOW, 2);
|
to.drawBorder(Rect::createAround(sel->pos, 2), Colors::BRIGHT_YELLOW, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
CCS->videoh->update(pos.x+70, pos.y+56, to.getInternalSurface(), true, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CTavernWindow::HeroPortrait::clickPressed(const Point & cursorPosition)
|
void CTavernWindow::HeroPortrait::clickPressed(const Point & cursorPosition)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class CHeroArea;
|
|||||||
class CAnimImage;
|
class CAnimImage;
|
||||||
class CFilledTexture;
|
class CFilledTexture;
|
||||||
class IImage;
|
class IImage;
|
||||||
|
class VideoWidget;
|
||||||
|
|
||||||
enum class EUserEvent;
|
enum class EUserEvent;
|
||||||
|
|
||||||
@@ -261,6 +262,7 @@ public:
|
|||||||
std::shared_ptr<CLabel> cost;
|
std::shared_ptr<CLabel> cost;
|
||||||
std::shared_ptr<CLabel> heroesForHire;
|
std::shared_ptr<CLabel> heroesForHire;
|
||||||
std::shared_ptr<CTextBox> heroDescription;
|
std::shared_ptr<CTextBox> heroDescription;
|
||||||
|
std::shared_ptr<VideoWidget> videoPlayer;
|
||||||
|
|
||||||
std::shared_ptr<CTextBox> rumor;
|
std::shared_ptr<CTextBox> rumor;
|
||||||
|
|
||||||
@@ -272,7 +274,6 @@ public:
|
|||||||
void addInvite();
|
void addInvite();
|
||||||
|
|
||||||
CTavernWindow(const CGObjectInstance * TavernObj, const std::function<void()> & onWindowClosed);
|
CTavernWindow(const CGObjectInstance * TavernObj, const std::function<void()> & onWindowClosed);
|
||||||
~CTavernWindow();
|
|
||||||
|
|
||||||
void close() override;
|
void close() override;
|
||||||
void recruitb();
|
void recruitb();
|
||||||
|
|||||||
@@ -11,9 +11,10 @@
|
|||||||
#include "GeneralOptionsTab.h"
|
#include "GeneralOptionsTab.h"
|
||||||
|
|
||||||
#include "CGameInfo.h"
|
#include "CGameInfo.h"
|
||||||
#include "CMusicHandler.h"
|
|
||||||
#include "CPlayerInterface.h"
|
#include "CPlayerInterface.h"
|
||||||
#include "CServerHandler.h"
|
#include "CServerHandler.h"
|
||||||
|
#include "media/IMusicPlayer.h"
|
||||||
|
#include "media/ISoundPlayer.h"
|
||||||
#include "render/IScreenHandler.h"
|
#include "render/IScreenHandler.h"
|
||||||
#include "windows/GUIClasses.h"
|
#include "windows/GUIClasses.h"
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ struct Options
|
|||||||
/// primary IETF language tag
|
/// primary IETF language tag
|
||||||
std::string tagIETF;
|
std::string tagIETF;
|
||||||
|
|
||||||
|
/// ISO 639-2 (B) language code
|
||||||
|
std::string tagISO2;
|
||||||
|
|
||||||
/// DateTime format
|
/// DateTime format
|
||||||
std::string dateTimeFormat;
|
std::string dateTimeFormat;
|
||||||
|
|
||||||
@@ -82,27 +85,27 @@ inline const auto & getLanguageList()
|
|||||||
{
|
{
|
||||||
static const std::array<Options, 20> languages
|
static const std::array<Options, 20> languages
|
||||||
{ {
|
{ {
|
||||||
{ "czech", "Czech", "Čeština", "CP1250", "cs", "%d.%m.%Y %T", EPluralForms::CZ_3, true },
|
{ "czech", "Czech", "Čeština", "CP1250", "cs", "cze", "%d.%m.%Y %T", EPluralForms::CZ_3, true },
|
||||||
{ "chinese", "Chinese", "简体中文", "GBK", "zh", "%F %T", EPluralForms::VI_1, true }, // Note: actually Simplified Chinese
|
{ "chinese", "Chinese", "简体中文", "GBK", "zh", "chi", "%F %T", EPluralForms::VI_1, true }, // Note: actually Simplified Chinese
|
||||||
{ "english", "English", "English", "CP1252", "en", "%F %T", EPluralForms::EN_2, true }, // English uses international date/time format here
|
{ "english", "English", "English", "CP1252", "en", "eng", "%F %T", EPluralForms::EN_2, true }, // English uses international date/time format here
|
||||||
{ "finnish", "Finnish", "Suomi", "CP1252", "fi", "%d.%m.%Y %T", EPluralForms::EN_2, true },
|
{ "finnish", "Finnish", "Suomi", "CP1252", "fi", "fin", "%d.%m.%Y %T", EPluralForms::EN_2, true },
|
||||||
{ "french", "French", "Français", "CP1252", "fr", "%d/%m/%Y %T", EPluralForms::FR_2, true },
|
{ "french", "French", "Français", "CP1252", "fr", "fre", "%d/%m/%Y %T", EPluralForms::FR_2, true },
|
||||||
{ "german", "German", "Deutsch", "CP1252", "de", "%d.%m.%Y %T", EPluralForms::EN_2, true },
|
{ "german", "German", "Deutsch", "CP1252", "de", "ger", "%d.%m.%Y %T", EPluralForms::EN_2, true },
|
||||||
{ "hungarian", "Hungarian", "Magyar", "CP1250", "hu", "%Y. %m. %d. %T", EPluralForms::EN_2, true },
|
{ "hungarian", "Hungarian", "Magyar", "CP1250", "hu", "hun", "%Y. %m. %d. %T", EPluralForms::EN_2, true },
|
||||||
{ "italian", "Italian", "Italiano", "CP1250", "it", "%d/%m/%Y %T", EPluralForms::EN_2, true },
|
{ "italian", "Italian", "Italiano", "CP1250", "it", "ita", "%d/%m/%Y %T", EPluralForms::EN_2, true },
|
||||||
{ "korean", "Korean", "한국어", "CP949", "ko", "%F %T", EPluralForms::VI_1, true },
|
{ "korean", "Korean", "한국어", "CP949", "ko", "kor", "%F %T", EPluralForms::VI_1, true },
|
||||||
{ "polish", "Polish", "Polski", "CP1250", "pl", "%d.%m.%Y %T", EPluralForms::PL_3, true },
|
{ "polish", "Polish", "Polski", "CP1250", "pl", "pol", "%d.%m.%Y %T", EPluralForms::PL_3, true },
|
||||||
{ "portuguese", "Portuguese", "Português", "CP1252", "pt", "%d/%m/%Y %T", EPluralForms::EN_2, true }, // Note: actually Brazilian Portuguese
|
{ "portuguese", "Portuguese", "Português", "CP1252", "pt", "por", "%d/%m/%Y %T", EPluralForms::EN_2, true }, // Note: actually Brazilian Portuguese
|
||||||
{ "russian", "Russian", "Русский", "CP1251", "ru", "%d.%m.%Y %T", EPluralForms::UK_3, true },
|
{ "russian", "Russian", "Русский", "CP1251", "ru", "rus", "%d.%m.%Y %T", EPluralForms::UK_3, true },
|
||||||
{ "spanish", "Spanish", "Español", "CP1252", "es", "%d/%m/%Y %T", EPluralForms::EN_2, true },
|
{ "spanish", "Spanish", "Español", "CP1252", "es", "spa", "%d/%m/%Y %T", EPluralForms::EN_2, true },
|
||||||
{ "swedish", "Swedish", "Svenska", "CP1252", "sv", "%F %T", EPluralForms::EN_2, true },
|
{ "swedish", "Swedish", "Svenska", "CP1252", "sv", "swe", "%F %T", EPluralForms::EN_2, true },
|
||||||
{ "turkish", "Turkish", "Türkçe", "CP1254", "tr", "%d.%m.%Y %T", EPluralForms::EN_2, true },
|
{ "turkish", "Turkish", "Türkçe", "CP1254", "tr", "tur", "%d.%m.%Y %T", EPluralForms::EN_2, true },
|
||||||
{ "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", "%d.%m.%Y %T", EPluralForms::UK_3, true },
|
{ "ukrainian", "Ukrainian", "Українська", "CP1251", "uk", "ukr", "%d.%m.%Y %T", EPluralForms::UK_3, true },
|
||||||
{ "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", "%d/%m/%Y %T", EPluralForms::VI_1, true }, // Fan translation uses special encoding
|
{ "vietnamese", "Vietnamese", "Tiếng Việt", "UTF-8", "vi", "vie", "%d/%m/%Y %T", EPluralForms::VI_1, true }, // Fan translation uses special encoding
|
||||||
|
|
||||||
{ "other_cp1250", "Other (East European)", "", "CP1250", "", "", EPluralForms::NONE, false },
|
{ "other_cp1250", "Other (East European)", "", "CP1250", "", "", "", EPluralForms::NONE, false },
|
||||||
{ "other_cp1251", "Other (Cyrillic Script)", "", "CP1251", "", "", EPluralForms::NONE, false },
|
{ "other_cp1251", "Other (Cyrillic Script)", "", "CP1251", "", "", "", EPluralForms::NONE, false },
|
||||||
{ "other_cp1252", "Other (West European)", "", "CP1252", "", "", EPluralForms::NONE, false }
|
{ "other_cp1252", "Other (West European)", "", "CP1252", "", "", "", EPluralForms::NONE, false }
|
||||||
} };
|
} };
|
||||||
static_assert(languages.size() == static_cast<size_t>(ELanguages::COUNT), "Languages array is missing a value!");
|
static_assert(languages.size() == static_cast<size_t>(ELanguages::COUNT), "Languages array is missing a value!");
|
||||||
|
|
||||||
|
|||||||
@@ -113,11 +113,8 @@ EResType EResTypeHelper::getTypeFromExtension(std::string extension)
|
|||||||
{".MP3", EResType::SOUND},
|
{".MP3", EResType::SOUND},
|
||||||
{".OGG", EResType::SOUND},
|
{".OGG", EResType::SOUND},
|
||||||
{".FLAC", EResType::SOUND},
|
{".FLAC", EResType::SOUND},
|
||||||
{".SMK", EResType::VIDEO},
|
{".SMK", EResType::VIDEO_LOW_QUALITY},
|
||||||
{".BIK", EResType::VIDEO},
|
{".BIK", EResType::VIDEO},
|
||||||
{".MJPG", EResType::VIDEO},
|
|
||||||
{".MPG", EResType::VIDEO},
|
|
||||||
{".AVI", EResType::VIDEO},
|
|
||||||
{".WEBM", EResType::VIDEO},
|
{".WEBM", EResType::VIDEO},
|
||||||
{".ZIP", EResType::ARCHIVE_ZIP},
|
{".ZIP", EResType::ARCHIVE_ZIP},
|
||||||
{".LOD", EResType::ARCHIVE_LOD},
|
{".LOD", EResType::ARCHIVE_LOD},
|
||||||
@@ -157,6 +154,7 @@ std::string EResTypeHelper::getEResTypeAsString(EResType type)
|
|||||||
MAP_ENUM(TTF_FONT)
|
MAP_ENUM(TTF_FONT)
|
||||||
MAP_ENUM(IMAGE)
|
MAP_ENUM(IMAGE)
|
||||||
MAP_ENUM(VIDEO)
|
MAP_ENUM(VIDEO)
|
||||||
|
MAP_ENUM(VIDEO_LOW_QUALITY)
|
||||||
MAP_ENUM(SOUND)
|
MAP_ENUM(SOUND)
|
||||||
MAP_ENUM(ARCHIVE_ZIP)
|
MAP_ENUM(ARCHIVE_ZIP)
|
||||||
MAP_ENUM(ARCHIVE_LOD)
|
MAP_ENUM(ARCHIVE_LOD)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ enum class EResType
|
|||||||
TTF_FONT,
|
TTF_FONT,
|
||||||
IMAGE,
|
IMAGE,
|
||||||
VIDEO,
|
VIDEO,
|
||||||
|
VIDEO_LOW_QUALITY,
|
||||||
SOUND,
|
SOUND,
|
||||||
ARCHIVE_VID,
|
ARCHIVE_VID,
|
||||||
ARCHIVE_ZIP,
|
ARCHIVE_ZIP,
|
||||||
|
|||||||
@@ -493,6 +493,7 @@ static std::string imageFile(const JsonNode & node)
|
|||||||
static std::string videoFile(const JsonNode & node)
|
static std::string videoFile(const JsonNode & node)
|
||||||
{
|
{
|
||||||
TEST_FILE(node.getModScope(), "Video/", node.String(), EResType::VIDEO);
|
TEST_FILE(node.getModScope(), "Video/", node.String(), EResType::VIDEO);
|
||||||
|
TEST_FILE(node.getModScope(), "Video/", node.String(), EResType::VIDEO_LOW_QUALITY);
|
||||||
return "Video file \"" + node.String() + "\" was not found";
|
return "Video file \"" + node.String() + "\" was not found";
|
||||||
}
|
}
|
||||||
#undef TEST_FILE
|
#undef TEST_FILE
|
||||||
|
|||||||
Reference in New Issue
Block a user