From 4d46a2084de450dfe0399477bcff932d238c5a27 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 16 Oct 2024 02:36:26 +0200 Subject: [PATCH 1/7] basic subtitle rendering --- client/media/CVideoHandler.cpp | 6 ++++++ client/media/CVideoHandler.h | 1 + client/media/IVideoPlayer.h | 3 +++ client/widgets/VideoWidget.cpp | 10 ++++++++++ client/widgets/VideoWidget.h | 2 ++ 5 files changed, 22 insertions(+) diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index b028e8c57..3932aa1d6 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -316,6 +316,12 @@ bool CVideoInstance::loadNextFrame() return true; } + +double CVideoInstance::timeStamp() +{ + return getCurrentFrameEndTime(); +} + bool CVideoInstance::videoEnded() { return getCurrentFrame() == nullptr; diff --git a/client/media/CVideoHandler.h b/client/media/CVideoHandler.h index d40582b62..42e48eaff 100644 --- a/client/media/CVideoHandler.h +++ b/client/media/CVideoHandler.h @@ -88,6 +88,7 @@ public: void openVideo(); bool loadNextFrame(); + double timeStamp() final; bool videoEnded() final; Point size() final; diff --git a/client/media/IVideoPlayer.h b/client/media/IVideoPlayer.h index 2c979a088..6f7380166 100644 --- a/client/media/IVideoPlayer.h +++ b/client/media/IVideoPlayer.h @@ -20,6 +20,9 @@ VCMI_LIB_NAMESPACE_END class IVideoInstance { public: + /// Returns current video timestamp + virtual double timeStamp() = 0; + /// Returns true if video playback is over virtual bool videoEnded() = 0; diff --git a/client/widgets/VideoWidget.cpp b/client/widgets/VideoWidget.cpp index 0fa6570cf..4d35e788d 100644 --- a/client/widgets/VideoWidget.cpp +++ b/client/widgets/VideoWidget.cpp @@ -9,6 +9,7 @@ */ #include "StdInc.h" #include "VideoWidget.h" +#include "TextControls.h" #include "../CGameInfo.h" #include "../gui/CGuiHandler.h" @@ -33,11 +34,14 @@ VideoWidgetBase::~VideoWidgetBase() = default; void VideoWidgetBase::playVideo(const VideoPath & fileToPlay) { + OBJECT_CONSTRUCTION; + videoInstance = CCS->videoh->open(fileToPlay, scaleFactor); if (videoInstance) { pos.w = videoInstance->size().x; pos.h = videoInstance->size().y; + subTitle = std::make_unique(Rect(0, (pos.h / 5) * 4, pos.w, pos.h / 5), EFonts::FONT_HIGH_SCORE, ETextAlignment::CENTER, Colors::WHITE, ""); } if (playAudio) @@ -52,6 +56,8 @@ void VideoWidgetBase::show(Canvas & to) { if(videoInstance) videoInstance->show(pos.topLeft(), to); + if(subTitle) + subTitle->showAll(to); } void VideoWidgetBase::loadAudio(const VideoPath & fileToPlay) @@ -107,6 +113,8 @@ void VideoWidgetBase::showAll(Canvas & to) { if(videoInstance) videoInstance->show(pos.topLeft(), to); + if(subTitle) + subTitle->showAll(to); } void VideoWidgetBase::tick(uint32_t msPassed) @@ -122,6 +130,8 @@ void VideoWidgetBase::tick(uint32_t msPassed) onPlaybackFinished(); } } + if(subTitle && videoInstance) + subTitle->setText(std::to_string(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + "\n" + std::to_string(msPassed) + "\n" + std::to_string(videoInstance->timeStamp())); } VideoWidget::VideoWidget(const Point & position, const VideoPath & prologue, const VideoPath & looped, bool playAudio) diff --git a/client/widgets/VideoWidget.h b/client/widgets/VideoWidget.h index b0264d58d..48d6fc42f 100644 --- a/client/widgets/VideoWidget.h +++ b/client/widgets/VideoWidget.h @@ -14,10 +14,12 @@ #include "../lib/filesystem/ResourcePath.h" class IVideoInstance; +class CMultiLineLabel; class VideoWidgetBase : public CIntObject { std::unique_ptr videoInstance; + std::unique_ptr subTitle; std::pair, si64> audioData = {nullptr, 0}; int audioHandle = -1; From 54542c54b3d104cb5afd2083ea10f5c3bf431280 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 16 Oct 2024 03:48:14 +0200 Subject: [PATCH 2/7] working subtitles --- client/widgets/VideoWidget.cpp | 48 +++++++++++++++++++++++++++++++-- client/widgets/VideoWidget.h | 2 ++ lib/filesystem/ResourcePath.cpp | 1 + lib/filesystem/ResourcePath.h | 2 ++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/client/widgets/VideoWidget.cpp b/client/widgets/VideoWidget.cpp index 4d35e788d..0db38a1d8 100644 --- a/client/widgets/VideoWidget.cpp +++ b/client/widgets/VideoWidget.cpp @@ -17,6 +17,8 @@ #include "../media/IVideoPlayer.h" #include "../render/Canvas.h" +#include "../../lib/filesystem/Filesystem.h" + VideoWidgetBase::VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio) : VideoWidgetBase(position, video, playAudio, 1.0) { @@ -36,6 +38,20 @@ void VideoWidgetBase::playVideo(const VideoPath & fileToPlay) { OBJECT_CONSTRUCTION; + using SubTitlePath = ResourcePathTempl; + SubTitlePath subTitlePath = fileToPlay.toType(); + SubTitlePath subTitlePathVideoDir = subTitlePath.addPrefix("VIDEO/"); + if(CResourceHandler::get()->existsResource(subTitlePath)) + { + auto rawData = CResourceHandler::get()->load(subTitlePath)->readAll(); + srtContent = std::string(reinterpret_cast(rawData.first.get()), rawData.second); + } + if(CResourceHandler::get()->existsResource(subTitlePathVideoDir)) + { + auto rawData = CResourceHandler::get()->load(subTitlePathVideoDir)->readAll(); + srtContent = std::string(reinterpret_cast(rawData.first.get()), rawData.second); + } + videoInstance = CCS->videoh->open(fileToPlay, scaleFactor); if (videoInstance) { @@ -83,7 +99,7 @@ void VideoWidgetBase::startAudio() { this->audioHandle = -1; } - ); + ); } } @@ -97,6 +113,34 @@ void VideoWidgetBase::stopAudio() } } +std::string VideoWidgetBase::getSubTitleLine(double timestamp) +{ + if(srtContent.empty()) + return ""; + + std::regex exp("^\\s*(\\d+:\\d+:\\d+,\\d+)[^\\S\\n]+-->[^\\S\\n]+(\\d+:\\d+:\\d+,\\d+)((?:\\n(?!\\d+:\\d+:\\d+,\\d+\\b|\\n+\\d+$).*)*)", std::regex::multiline); + std::smatch res; + + std::string::const_iterator searchStart(srtContent.cbegin()); + while (std::regex_search(searchStart, srtContent.cend(), res, exp)) + { + std::vector timestamp1Str; + boost::split(timestamp1Str, static_cast(res[1]), boost::is_any_of(":,")); + std::vector timestamp2Str; + boost::split(timestamp2Str, static_cast(res[2]), boost::is_any_of(":,")); + double timestamp1 = std::stoi(timestamp1Str[0]) * 3600 + std::stoi(timestamp1Str[1]) * 60 + std::stoi(timestamp1Str[2]) + (std::stoi(timestamp1Str[3]) / 1000.0); + double timestamp2 = std::stoi(timestamp2Str[0]) * 3600 + std::stoi(timestamp2Str[1]) * 60 + std::stoi(timestamp2Str[2]) + (std::stoi(timestamp2Str[3]) / 1000.0); + std::string text = res[3]; + text.erase(0, text.find_first_not_of("\r\n\t ")); + + if(timestamp > timestamp1 && timestamp < timestamp2) + return text; + + searchStart = res.suffix().first; + } + return ""; +} + void VideoWidgetBase::activate() { CIntObject::activate(); @@ -131,7 +175,7 @@ void VideoWidgetBase::tick(uint32_t msPassed) } } if(subTitle && videoInstance) - subTitle->setText(std::to_string(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + "\n" + std::to_string(msPassed) + "\n" + std::to_string(videoInstance->timeStamp())); + subTitle->setText(getSubTitleLine(videoInstance->timeStamp())); } VideoWidget::VideoWidget(const Point & position, const VideoPath & prologue, const VideoPath & looped, bool playAudio) diff --git a/client/widgets/VideoWidget.h b/client/widgets/VideoWidget.h index 48d6fc42f..465e92f1b 100644 --- a/client/widgets/VideoWidget.h +++ b/client/widgets/VideoWidget.h @@ -25,10 +25,12 @@ class VideoWidgetBase : public CIntObject int audioHandle = -1; bool playAudio = false; float scaleFactor = 1.0; + std::string srtContent = ""; void loadAudio(const VideoPath & file); void startAudio(); void stopAudio(); + std::string getSubTitleLine(double timestamp); protected: VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio); diff --git a/lib/filesystem/ResourcePath.cpp b/lib/filesystem/ResourcePath.cpp index 347c3e9fd..b29d24954 100644 --- a/lib/filesystem/ResourcePath.cpp +++ b/lib/filesystem/ResourcePath.cpp @@ -117,6 +117,7 @@ EResType EResTypeHelper::getTypeFromExtension(std::string extension) {".BIK", EResType::VIDEO}, {".OGV", EResType::VIDEO}, {".WEBM", EResType::VIDEO}, + {".SRT", EResType::SUBTITLE}, {".ZIP", EResType::ARCHIVE_ZIP}, {".LOD", EResType::ARCHIVE_LOD}, {".PAC", EResType::ARCHIVE_LOD}, diff --git a/lib/filesystem/ResourcePath.h b/lib/filesystem/ResourcePath.h index 4f4b4e9a1..ba594d6ff 100644 --- a/lib/filesystem/ResourcePath.h +++ b/lib/filesystem/ResourcePath.h @@ -30,6 +30,7 @@ class JsonSerializeFormat; * Sound: .wav .82m * Video: .smk, .bik .ogv .webm * Music: .mp3, .ogg + * Subtitle: .srt * Archive: .lod, .snd, .vid .pac .zip * Palette: .pal * Savegame: .v*gm1 @@ -48,6 +49,7 @@ enum class EResType VIDEO, VIDEO_LOW_QUALITY, SOUND, + SUBTITLE, ARCHIVE_VID, ARCHIVE_ZIP, ARCHIVE_SND, From 8b427c3989f6cc228155c5ce76b8a1fad99ac8b6 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:37:18 +0200 Subject: [PATCH 3/7] use json format --- client/widgets/VideoWidget.cpp | 49 +++++++++----------------------- client/widgets/VideoWidget.h | 3 +- docs/translators/Translations.md | 13 +++++++++ lib/filesystem/ResourcePath.cpp | 1 - lib/filesystem/ResourcePath.h | 2 -- 5 files changed, 28 insertions(+), 40 deletions(-) diff --git a/client/widgets/VideoWidget.cpp b/client/widgets/VideoWidget.cpp index 0db38a1d8..348548935 100644 --- a/client/widgets/VideoWidget.cpp +++ b/client/widgets/VideoWidget.cpp @@ -38,26 +38,19 @@ void VideoWidgetBase::playVideo(const VideoPath & fileToPlay) { OBJECT_CONSTRUCTION; - using SubTitlePath = ResourcePathTempl; - SubTitlePath subTitlePath = fileToPlay.toType(); - SubTitlePath subTitlePathVideoDir = subTitlePath.addPrefix("VIDEO/"); + JsonPath subTitlePath = fileToPlay.toType(); + JsonPath subTitlePathVideoDir = subTitlePath.addPrefix("VIDEO/"); if(CResourceHandler::get()->existsResource(subTitlePath)) - { - auto rawData = CResourceHandler::get()->load(subTitlePath)->readAll(); - srtContent = std::string(reinterpret_cast(rawData.first.get()), rawData.second); - } - if(CResourceHandler::get()->existsResource(subTitlePathVideoDir)) - { - auto rawData = CResourceHandler::get()->load(subTitlePathVideoDir)->readAll(); - srtContent = std::string(reinterpret_cast(rawData.first.get()), rawData.second); - } + subTitleData = JsonNode(subTitlePath); + else if(CResourceHandler::get()->existsResource(subTitlePathVideoDir)) + subTitleData = JsonNode(subTitlePathVideoDir); videoInstance = CCS->videoh->open(fileToPlay, scaleFactor); if (videoInstance) { pos.w = videoInstance->size().x; pos.h = videoInstance->size().y; - subTitle = std::make_unique(Rect(0, (pos.h / 5) * 4, pos.w, pos.h / 5), EFonts::FONT_HIGH_SCORE, ETextAlignment::CENTER, Colors::WHITE, ""); + subTitle = std::make_unique(Rect(0, (pos.h / 5) * 4, pos.w, pos.h / 5), EFonts::FONT_HIGH_SCORE, ETextAlignment::CENTER, Colors::WHITE); } if (playAudio) @@ -115,30 +108,14 @@ void VideoWidgetBase::stopAudio() std::string VideoWidgetBase::getSubTitleLine(double timestamp) { - if(srtContent.empty()) - return ""; + if(subTitleData.isNull()) + return {}; + + for(auto & segment : subTitleData.Vector()) + if(timestamp > segment["timeStart"].Float() && timestamp < segment["timeEnd"].Float()) + return segment["text"].String(); - std::regex exp("^\\s*(\\d+:\\d+:\\d+,\\d+)[^\\S\\n]+-->[^\\S\\n]+(\\d+:\\d+:\\d+,\\d+)((?:\\n(?!\\d+:\\d+:\\d+,\\d+\\b|\\n+\\d+$).*)*)", std::regex::multiline); - std::smatch res; - - std::string::const_iterator searchStart(srtContent.cbegin()); - while (std::regex_search(searchStart, srtContent.cend(), res, exp)) - { - std::vector timestamp1Str; - boost::split(timestamp1Str, static_cast(res[1]), boost::is_any_of(":,")); - std::vector timestamp2Str; - boost::split(timestamp2Str, static_cast(res[2]), boost::is_any_of(":,")); - double timestamp1 = std::stoi(timestamp1Str[0]) * 3600 + std::stoi(timestamp1Str[1]) * 60 + std::stoi(timestamp1Str[2]) + (std::stoi(timestamp1Str[3]) / 1000.0); - double timestamp2 = std::stoi(timestamp2Str[0]) * 3600 + std::stoi(timestamp2Str[1]) * 60 + std::stoi(timestamp2Str[2]) + (std::stoi(timestamp2Str[3]) / 1000.0); - std::string text = res[3]; - text.erase(0, text.find_first_not_of("\r\n\t ")); - - if(timestamp > timestamp1 && timestamp < timestamp2) - return text; - - searchStart = res.suffix().first; - } - return ""; + return {}; } void VideoWidgetBase::activate() diff --git a/client/widgets/VideoWidget.h b/client/widgets/VideoWidget.h index 465e92f1b..4e2672d68 100644 --- a/client/widgets/VideoWidget.h +++ b/client/widgets/VideoWidget.h @@ -12,6 +12,7 @@ #include "../gui/CIntObject.h" #include "../lib/filesystem/ResourcePath.h" +#include "../lib/json/JsonNode.h" class IVideoInstance; class CMultiLineLabel; @@ -25,7 +26,7 @@ class VideoWidgetBase : public CIntObject int audioHandle = -1; bool playAudio = false; float scaleFactor = 1.0; - std::string srtContent = ""; + JsonNode subTitleData; void loadAudio(const VideoPath & file); void startAudio(); diff --git a/docs/translators/Translations.md b/docs/translators/Translations.md index 89e6cbba8..fed671632 100644 --- a/docs/translators/Translations.md +++ b/docs/translators/Translations.md @@ -56,6 +56,19 @@ This will export all strings from game into `Documents/My Games/VCMI/extracted/t To export maps and campaigns, use '/translate maps' command instead. +### Video subtitles +It's possible to add video subtitles. Create a JSON file in `video` folder of translation mod with the name of the video (e.g. `H3Intro.json`): +``` +[ + { + "timeStart" : 5.640, // start time, seconds + "timeEnd" : 8.120, // end time, seconds + "text" : " ... " // text to show during this period + }, + ... +] +``` + ## Translating VCMI data VCMI contains several new strings, to cover functionality not existing in Heroes III. It can be roughly split into following parts: diff --git a/lib/filesystem/ResourcePath.cpp b/lib/filesystem/ResourcePath.cpp index b29d24954..347c3e9fd 100644 --- a/lib/filesystem/ResourcePath.cpp +++ b/lib/filesystem/ResourcePath.cpp @@ -117,7 +117,6 @@ EResType EResTypeHelper::getTypeFromExtension(std::string extension) {".BIK", EResType::VIDEO}, {".OGV", EResType::VIDEO}, {".WEBM", EResType::VIDEO}, - {".SRT", EResType::SUBTITLE}, {".ZIP", EResType::ARCHIVE_ZIP}, {".LOD", EResType::ARCHIVE_LOD}, {".PAC", EResType::ARCHIVE_LOD}, diff --git a/lib/filesystem/ResourcePath.h b/lib/filesystem/ResourcePath.h index ba594d6ff..4f4b4e9a1 100644 --- a/lib/filesystem/ResourcePath.h +++ b/lib/filesystem/ResourcePath.h @@ -30,7 +30,6 @@ class JsonSerializeFormat; * Sound: .wav .82m * Video: .smk, .bik .ogv .webm * Music: .mp3, .ogg - * Subtitle: .srt * Archive: .lod, .snd, .vid .pac .zip * Palette: .pal * Savegame: .v*gm1 @@ -49,7 +48,6 @@ enum class EResType VIDEO, VIDEO_LOW_QUALITY, SOUND, - SUBTITLE, ARCHIVE_VID, ARCHIVE_ZIP, ARCHIVE_SND, From f0b7d007a0de060a45632ebd11ea2254b469c04a Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 18 Oct 2024 03:30:22 +0200 Subject: [PATCH 4/7] video: use global timer; implement frameskip --- client/media/CVideoHandler.cpp | 12 ++++++++++-- client/media/CVideoHandler.h | 6 ++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index 3932aa1d6..41e2eb6a6 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -173,6 +173,7 @@ void CVideoInstance::openVideo() { openContext(); openCodec(findVideoStream()); + startTime = std::chrono::high_resolution_clock::now(); } void CVideoInstance::prepareOutput(float scaleFactor, bool useTextureOutput) @@ -391,9 +392,16 @@ void CVideoInstance::tick(uint32_t msPassed) if(videoEnded()) throw std::runtime_error("Video already ended!"); - frameTime += msPassed / 1000.0; + auto nowTime = std::chrono::high_resolution_clock::now(); + double difference = std::chrono::duration_cast(nowTime - startTime).count() / 1000.0; - if(frameTime >= getCurrentFrameEndTime()) + int frameskipCounter = 0; + while(!videoEnded() && difference >= getCurrentFrameEndTime() + getCurrentFrameDuration() && frameskipCounter < MAX_FRAMESKIP) // Frameskip + { + decodeNextFrame(); + frameskipCounter++; + } + if(!videoEnded() && difference >= getCurrentFrameEndTime()) loadNextFrame(); } diff --git a/client/media/CVideoHandler.h b/client/media/CVideoHandler.h index 42e48eaff..b4a3c7c81 100644 --- a/client/media/CVideoHandler.h +++ b/client/media/CVideoHandler.h @@ -77,10 +77,12 @@ class CVideoInstance final : public IVideoInstance, public FFMpegStream SDL_Surface * surface = nullptr; Point dimensions; - /// video playback current progress, in seconds - double frameTime = 0.0; + /// video playback start time point + std::chrono::high_resolution_clock::time_point startTime; void prepareOutput(float scaleFactor, bool useTextureOutput); + + const int MAX_FRAMESKIP = 5; public: ~CVideoInstance(); From 0f41361873ce1ebbf57623e2d7ae88aa1bdaab05 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:33:29 +0200 Subject: [PATCH 5/7] fix edge case --- client/media/CVideoHandler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index 41e2eb6a6..48aa18a04 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -173,7 +173,6 @@ void CVideoInstance::openVideo() { openContext(); openCodec(findVideoStream()); - startTime = std::chrono::high_resolution_clock::now(); } void CVideoInstance::prepareOutput(float scaleFactor, bool useTextureOutput) @@ -392,6 +391,9 @@ void CVideoInstance::tick(uint32_t msPassed) if(videoEnded()) throw std::runtime_error("Video already ended!"); + if(startTime == std::chrono::high_resolution_clock()) + startTime = std::chrono::high_resolution_clock::now(); + auto nowTime = std::chrono::high_resolution_clock::now(); double difference = std::chrono::duration_cast(nowTime - startTime).count() / 1000.0; From a68522b370d5d6daba037806abdebb0a9ba71cec Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:37:18 +0200 Subject: [PATCH 6/7] fix --- client/media/CVideoHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index 48aa18a04..809af5b26 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -391,7 +391,7 @@ void CVideoInstance::tick(uint32_t msPassed) if(videoEnded()) throw std::runtime_error("Video already ended!"); - if(startTime == std::chrono::high_resolution_clock()) + if(startTime == std::chrono::high_resolution_clock::time_point()) startTime = std::chrono::high_resolution_clock::now(); auto nowTime = std::chrono::high_resolution_clock::now(); From bb73a35412b2cc06ee2ec04d997eede85c9fe12f Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Wed, 30 Oct 2024 00:35:50 +0100 Subject: [PATCH 7/7] code review + pause handling --- client/media/CSoundHandler.cpp | 12 ++++++++++++ client/media/CSoundHandler.h | 2 ++ client/media/CVideoHandler.cpp | 22 +++++++++++++++++++--- client/media/CVideoHandler.h | 5 ++++- client/media/ISoundPlayer.h | 2 ++ client/media/IVideoPlayer.h | 4 ++++ client/widgets/VideoWidget.cpp | 14 +++++++++++--- 7 files changed, 54 insertions(+), 7 deletions(-) diff --git a/client/media/CSoundHandler.cpp b/client/media/CSoundHandler.cpp index adfb1a2f7..bd099e728 100644 --- a/client/media/CSoundHandler.cpp +++ b/client/media/CSoundHandler.cpp @@ -240,6 +240,18 @@ void CSoundHandler::stopSound(int handler) Mix_HaltChannel(handler); } +void CSoundHandler::pauseSound(int handler) +{ + if(isInitialized() && handler != -1) + Mix_Pause(handler); +} + +void CSoundHandler::resumeSound(int handler) +{ + if(isInitialized() && handler != -1) + Mix_Resume(handler); +} + ui32 CSoundHandler::getVolume() const { return volume; diff --git a/client/media/CSoundHandler.h b/client/media/CSoundHandler.h index 5a10a5493..3450cbffb 100644 --- a/client/media/CSoundHandler.h +++ b/client/media/CSoundHandler.h @@ -67,6 +67,8 @@ public: int playSound(std::pair, si64> & data, int repeats = 0, bool cache = false) final; int playSoundFromSet(std::vector & sound_vec) final; void stopSound(int handler) final; + void pauseSound(int handler) final; + void resumeSound(int handler) final; void setCallback(int channel, std::function function) final; void resetCallback(int channel) final; diff --git a/client/media/CVideoHandler.cpp b/client/media/CVideoHandler.cpp index 809af5b26..a2a0ef939 100644 --- a/client/media/CVideoHandler.cpp +++ b/client/media/CVideoHandler.cpp @@ -391,10 +391,10 @@ void CVideoInstance::tick(uint32_t msPassed) if(videoEnded()) throw std::runtime_error("Video already ended!"); - if(startTime == std::chrono::high_resolution_clock::time_point()) - startTime = std::chrono::high_resolution_clock::now(); + if(startTime == std::chrono::steady_clock::time_point()) + startTime = std::chrono::steady_clock::now(); - auto nowTime = std::chrono::high_resolution_clock::now(); + auto nowTime = std::chrono::steady_clock::now(); double difference = std::chrono::duration_cast(nowTime - startTime).count() / 1000.0; int frameskipCounter = 0; @@ -407,6 +407,22 @@ void CVideoInstance::tick(uint32_t msPassed) loadNextFrame(); } + +void CVideoInstance::activate() +{ + if(deactivationStartTime != std::chrono::steady_clock::time_point()) + { + auto pauseDuration = std::chrono::steady_clock::now() - deactivationStartTime; + startTime += pauseDuration; + deactivationStartTime = std::chrono::steady_clock::time_point(); + } +} + +void CVideoInstance::deactivate() +{ + deactivationStartTime = std::chrono::steady_clock::now(); +} + struct FFMpegFormatDescription { uint8_t sampleSizeBytes; diff --git a/client/media/CVideoHandler.h b/client/media/CVideoHandler.h index b4a3c7c81..63e4a6176 100644 --- a/client/media/CVideoHandler.h +++ b/client/media/CVideoHandler.h @@ -78,7 +78,8 @@ class CVideoInstance final : public IVideoInstance, public FFMpegStream Point dimensions; /// video playback start time point - std::chrono::high_resolution_clock::time_point startTime; + std::chrono::steady_clock::time_point startTime; + std::chrono::steady_clock::time_point deactivationStartTime; void prepareOutput(float scaleFactor, bool useTextureOutput); @@ -96,6 +97,8 @@ public: void show(const Point & position, Canvas & canvas) final; void tick(uint32_t msPassed) final; + void activate() final; + void deactivate() final; }; class CVideoPlayer final : public IVideoPlayer diff --git a/client/media/ISoundPlayer.h b/client/media/ISoundPlayer.h index 9b3d9d5e9..ffcb90dce 100644 --- a/client/media/ISoundPlayer.h +++ b/client/media/ISoundPlayer.h @@ -22,6 +22,8 @@ public: virtual int playSound(std::pair, si64> & data, int repeats = 0, bool cache = false) = 0; virtual int playSoundFromSet(std::vector & sound_vec) = 0; virtual void stopSound(int handler) = 0; + virtual void pauseSound(int handler) = 0; + virtual void resumeSound(int handler) = 0; virtual ui32 getVolume() const = 0; virtual void setVolume(ui32 percent) = 0; diff --git a/client/media/IVideoPlayer.h b/client/media/IVideoPlayer.h index 6f7380166..3f2784c16 100644 --- a/client/media/IVideoPlayer.h +++ b/client/media/IVideoPlayer.h @@ -35,6 +35,10 @@ public: /// Advances video playback by specified duration virtual void tick(uint32_t msPassed) = 0; + /// activate or deactivate video + virtual void activate() = 0; + virtual void deactivate() = 0; + virtual ~IVideoInstance() = default; }; diff --git a/client/widgets/VideoWidget.cpp b/client/widgets/VideoWidget.cpp index 348548935..35fe4adcb 100644 --- a/client/widgets/VideoWidget.cpp +++ b/client/widgets/VideoWidget.cpp @@ -50,7 +50,8 @@ void VideoWidgetBase::playVideo(const VideoPath & fileToPlay) { pos.w = videoInstance->size().x; pos.h = videoInstance->size().y; - subTitle = std::make_unique(Rect(0, (pos.h / 5) * 4, pos.w, pos.h / 5), EFonts::FONT_HIGH_SCORE, ETextAlignment::CENTER, Colors::WHITE); + if(!subTitleData.isNull()) + subTitle = std::make_unique(Rect(0, (pos.h / 5) * 4, pos.w, pos.h / 5), EFonts::FONT_HIGH_SCORE, ETextAlignment::CENTER, Colors::WHITE); } if (playAudio) @@ -121,13 +122,20 @@ std::string VideoWidgetBase::getSubTitleLine(double timestamp) void VideoWidgetBase::activate() { CIntObject::activate(); - startAudio(); + if(audioHandle != -1) + CCS->soundh->resumeSound(audioHandle); + else + startAudio(); + if(videoInstance) + videoInstance->activate(); } void VideoWidgetBase::deactivate() { CIntObject::deactivate(); - stopAudio(); + CCS->soundh->pauseSound(audioHandle); + if(videoInstance) + videoInstance->deactivate(); } void VideoWidgetBase::showAll(Canvas & to)