Browse Source

Added VideoWidget to hide implementation details

Ivan Savenko 1 year ago
parent
commit
d08c7b7b8f

+ 2 - 0
client/CMakeLists.txt

@@ -131,6 +131,7 @@ set(client_SRCS
 	widgets/CArtifactsOfHeroMarket.cpp
 	widgets/CArtifactsOfHeroBackpack.cpp
 	widgets/RadialMenu.cpp
+	widgets/VideoWidget.cpp
 	widgets/markets/CAltarArtifacts.cpp
 	widgets/markets/CAltarCreatures.cpp
 	widgets/markets/CArtifactsBuying.cpp
@@ -332,6 +333,7 @@ set(client_HEADERS
 	widgets/CArtifactsOfHeroMarket.h
 	widgets/CArtifactsOfHeroBackpack.h
 	widgets/RadialMenu.h
+	widgets/VideoWidget.h
 	widgets/markets/CAltarArtifacts.h
 	widgets/markets/CAltarCreatures.h
 	widgets/markets/CArtifactsBuying.h

+ 61 - 107
client/battle/BattleInterfaceClasses.cpp

@@ -26,7 +26,6 @@
 #include "../gui/MouseButton.h"
 #include "../gui/WindowHandler.h"
 #include "../media/IMusicPlayer.h"
-#include "../media/IVideoPlayer.h"
 #include "../render/Canvas.h"
 #include "../render/IImage.h"
 #include "../render/IFont.h"
@@ -35,6 +34,7 @@
 #include "../widgets/Images.h"
 #include "../widgets/Slider.h"
 #include "../widgets/TextControls.h"
+#include "../widgets/VideoWidget.h"
 #include "../widgets/GraphicalPrimitiveCanvas.h"
 #include "../windows/CMessage.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)
-	: owner(_owner), currentVideo(BattleResultVideo::NONE)
+	: owner(_owner)
 {
 	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);
+
+	CCS->musich->playMusic(resources.musicName, false, true);
+}
+
+BattleResultResources BattleResultWindow::getResources(const BattleResult & br)
+{
 	//printing result description
 	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;
-		currentVideo = BattleResultVideo::WIN;
+		if(isSiege && weAreDefender)
+		{
+			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)
 		{
 		case EBattleResult::NORMAL:
-			if(owner.cb->getBattle(br.battleID)->battleGetDefendedTown() && !weAreAttacker)
-				currentVideo = BattleResultVideo::WIN_SIEGE;
+			resources.resultText.appendTextID("core.genrltxt.304");
 			break;
 		case EBattleResult::ESCAPE:
-			text = 303;
+			resources.resultText.appendTextID("core.genrltxt.303");
 			break;
 		case EBattleResult::SURRENDER:
-			text = 302;
+			resources.resultText.appendTextID("core.genrltxt.302");
 			break;
 		default:
-			logGlobal->error("Invalid battle result code %d. Assumed normal.", static_cast<int>(br.result));
-			break;
+			throw std::runtime_error("Invalid battle result!");
 		}
-		playVideo();
-
-		std::string str = CGI->generaltexth->allTexts[text];
 
 		const CGHeroInstance * ourHero = owner.cb->getBattle(br.battleID)->battleGetMyHero();
 		if (ourHero)
 		{
-			str += CGI->generaltexth->allTexts[305];
-			boost::algorithm::replace_first(str, "%s", ourHero->getNameTranslated());
-			boost::algorithm::replace_first(str, "%d", std::to_string(br.exp[weAreAttacker ? 0 : 1]));
+			resources.resultText.appendTextID("core.genrltxt.305");
+			resources.resultText.replaceTextID(ourHero->getNameTranslated());
+			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
 	{
-		int text = 311;
-		currentVideo = BattleResultVideo::DEFEAT;
 		switch(br.result)
 		{
 		case EBattleResult::NORMAL:
-			if(owner.cb->getBattle(br.battleID)->battleGetDefendedTown() && !weAreAttacker)
-				currentVideo = BattleResultVideo::DEFEAT_SIEGE;
+			resources.resultText.appendTextID("core.genrltxt.311");
+			resources.musicName = AudioPath::builtin("Music/LoseCombat");
+			resources.prologueVideo = VideoPath::builtin("LBSTART.BIK");
+			resources.loopedVideo = VideoPath::builtin("LBLOOP.BIK");
 			break;
 		case EBattleResult::ESCAPE:
-			currentVideo = BattleResultVideo::RETREAT;
-			text = 310;
+			resources.resultText.appendTextID("core.genrltxt.310");
+			resources.musicName = AudioPath::builtin("Music/Retreat Battle");
+			resources.prologueVideo = VideoPath::builtin("RTSTART.BIK");
+			resources.loopedVideo = VideoPath::builtin("RTLOOP.BIK");
 			break;
 		case EBattleResult::SURRENDER:
-			currentVideo = BattleResultVideo::SURRENDER;
-			text = 309;
+			resources.resultText.appendTextID("core.genrltxt.309");
+			resources.musicName = AudioPath::builtin("Music/Surrender Battle");
+			resources.prologueVideo = VideoPath();
+			resources.loopedVideo = VideoPath::builtin("SURRENDER.BIK");
 			break;
 		default:
-			logGlobal->error("Invalid battle result code %d. Assumed normal.", static_cast<int>(br.result));
-			break;
+				throw std::runtime_error("Invalid battle result!");
 		}
-		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()
@@ -775,81 +805,6 @@ void BattleResultWindow::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)
 {
 	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,
 	//so we can be sure that there is no dialogs left on GUI stack.
 	intTmp.showingDialog->setn(false);
-	CCS->videoh->close();
 }
 
 void BattleResultWindow::bExitf()

+ 12 - 18
client/battle/BattleInterfaceClasses.h

@@ -14,6 +14,7 @@
 #include "../../lib/FunctionList.h"
 #include "../../lib/battle/BattleHex.h"
 #include "../windows/CWindowObject.h"
+#include "../../lib/MetaString.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -42,6 +43,7 @@ class CAnimImage;
 class TransparentFilledRectangle;
 class CPlayerInterface;
 class BattleRenderer;
+class VideoWidget;
 
 /// 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
@@ -185,6 +187,14 @@ public:
 	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 BattleResultWindow : public WindowBase
 {
@@ -195,25 +205,10 @@ private:
 	std::shared_ptr<CButton> repeat;
 	std::vector<std::shared_ptr<CAnimImage>> icons;
 	std::shared_ptr<CTextBox> description;
+	std::shared_ptr<VideoWidget> videoPlayer;
 	CPlayerInterface & owner;
 
-	enum BattleResultVideo
-	{
-		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);
+	BattleResultResources getResources(const BattleResult & br);
 	
 	void buttonPressed(int button); //internal function for button callbacks
 public:
@@ -224,7 +219,6 @@ public:
 	std::function<void(int result)> resultCallback; //callback receiving which button was pressed
 
 	void activate() override;
-	void show(Canvas & to) override;
 };
 
 /// Shows the stack queue

+ 7 - 19
client/mainmenu/CCampaignScreen.cpp

@@ -19,13 +19,13 @@
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
 #include "../media/IMusicPlayer.h"
-#include "../media/IVideoPlayer.h"
 #include "../render/Canvas.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/ObjectLists.h"
 #include "../widgets/TextControls.h"
+#include "../widgets/VideoWidget.h"
 #include "../windows/GUIClasses.h"
 #include "../windows/InfoWindows.h"
 #include "../windows/CWindowObject.h"
@@ -100,7 +100,7 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config, const
 	pos.h = 116;
 
 	campFile = config["file"].String();
-	video = VideoPath::fromJson(config["video"]);
+	videoPath = VideoPath::fromJson(config["video"]);
 
 	status = CCampaignScreen::ENABLED;
 
@@ -127,7 +127,6 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config, const
 	{
 		addUsedEvents(LCLICK | HOVER);
 		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, "");
 		parent->addChild(hoverLabel.get());
 	}
@@ -136,30 +135,19 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config, const
 		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)
 {
-	CCS->videoh->close();
 	CMainMenu::openCampaignLobby(campFile, campaignSet);
 }
 
 void CCampaignScreen::CCampaignButton::hover(bool on)
 {
-	if (on)
-		CCS->videoh->open(video);
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	if (on && !videoPath.empty())
+		videoPlayer = std::make_shared<VideoWidget>(Point(), videoPath);
 	else
-		CCS->videoh->close();
+		videoPlayer.reset();
 
 	if(hoverLabel)
 	{

+ 3 - 2
client/mainmenu/CCampaignScreen.h

@@ -20,6 +20,7 @@ VCMI_LIB_NAMESPACE_END
 class CLabel;
 class CPicture;
 class CButton;
+class VideoWidget;
 
 class CCampaignScreen : public CWindowObject
 {
@@ -34,10 +35,11 @@ private:
 		std::shared_ptr<CLabel> hoverLabel;
 		std::shared_ptr<CPicture> graphicsImage;
 		std::shared_ptr<CPicture> graphicsCompleted;
+		std::shared_ptr<VideoWidget> videoPlayer;
 		CampaignStatus status;
+		VideoPath videoPath;
 
 		std::string campFile; // the filename/resourcename of the campaign
-		VideoPath video; // the resource name of the video
 		std::string hoverText;
 
 		std::string campaignSet;
@@ -47,7 +49,6 @@ private:
 
 	public:
 		CCampaignButton(const JsonNode & config, const JsonNode & parentConfig, std::string campaignSet);
-		void show(Canvas & to) override;
 	};
 
 	std::string campaignSet;

+ 7 - 45
client/mainmenu/CHighScoreScreen.cpp

@@ -16,11 +16,11 @@
 #include "../gui/Shortcut.h"
 #include "../media/IMusicPlayer.h"
 #include "../media/ISoundPlayer.h"
-#include "../media/IVideoPlayer.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/CTextInput.h"
 #include "../widgets/Images.h"
 #include "../widgets/GraphicalPrimitiveCanvas.h"
+#include "../widgets/VideoWidget.h"
 #include "../windows/InfoWindows.h"
 #include "../widgets/TextControls.h"
 #include "../render/Canvas.h"
@@ -217,7 +217,7 @@ void CHighScoreScreen::buttonExitClick()
 }
 
 CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc)
-	: CWindowObject(BORDERED), won(won), calc(calc), videoSoundHandle(-1)
+	: CWindowObject(BORDERED), won(won), calc(calc)
 {
 	addUsedEvents(LCLICK | KEYBOARD);
 
@@ -229,6 +229,8 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc
 
 	if(won)
 	{
+		videoPlayer = std::make_shared<VideoWidget>(Point(30, 120), VideoPath::builtin("HSANIM.SMK"), VideoPath::builtin("HSLOOP.SMK"));
+
 		int border = 100;
 		int textareaW = ((pos.w - 2 * border) / 4);
 		std::vector<std::string> t = { "438", "439", "440", "441", "676" }; // time, score, difficulty, final score, rank
@@ -243,9 +245,10 @@ CHighScoreInputScreen::CHighScoreInputScreen(bool won, HighScoreCalculation calc
 		CCS->musich->playMusic(AudioPath::builtin("music/Win Scenario"), true, true);
 	}
 	else
+	{
+		videoPlayer = std::make_shared<VideoWidget>(Point(30, 120), VideoPath::builtin("HSANIM.SMK"), VideoPath::builtin("LOSEGAME.SMK"));
 		CCS->musich->playMusic(AudioPath::builtin("music/UltimateLose"), false, true);
-
-	video = won ? "HSANIM.SMK" : "LOSEGAME.SMK";
+	}
 }
 
 int CHighScoreInputScreen::addEntry(std::string text) {
@@ -288,47 +291,6 @@ int CHighScoreInputScreen::addEntry(std::string text) {
 	return pos;
 }
 
-void CHighScoreInputScreen::show(Canvas & to)
-{
-	CCS->videoh->update(pos.x, pos.y, to.getInternalSurface(), true, false,
-	[&]()
-	{
-		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)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;

+ 3 - 6
client/mainmenu/CHighScoreScreen.h

@@ -15,6 +15,7 @@ class CLabel;
 class CMultiLineLabel;
 class CAnimImage;
 class CTextInput;
+class VideoWidget;
 
 class TransparentFilledRectangle;
 
@@ -93,9 +94,8 @@ class CHighScoreInputScreen : public CWindowObject
 	std::vector<std::shared_ptr<CMultiLineLabel>> texts;
 	std::shared_ptr<CHighScoreInput> input;
 	std::shared_ptr<TransparentFilledRectangle> background;
+	std::shared_ptr<VideoWidget> videoPlayer;
 
-	std::string video;
-	int videoSoundHandle;
 	bool won;
 	HighScoreCalculation calc;
 public:
@@ -103,9 +103,6 @@ public:
 
 	int addEntry(std::string text);
 
-	void show(Canvas & to) override;
-	void activate() override;
-	void deactivate() override;
 	void clickPressed(const Point & cursorPosition) override;
 	void keyPressed(EShortcut key) override;
-};
+};

+ 8 - 24
client/mainmenu/CMainMenu.cpp

@@ -18,7 +18,6 @@
 #include "../lobby/CSelectionBase.h"
 #include "../lobby/CLobbyScreen.h"
 #include "../media/IMusicPlayer.h"
-#include "../media/IVideoPlayer.h"
 #include "../gui/CursorHandler.h"
 #include "../windows/GUIClasses.h"
 #include "../gui/CGuiHandler.h"
@@ -35,6 +34,7 @@
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/ObjectLists.h"
 #include "../widgets/TextControls.h"
+#include "../widgets/VideoWidget.h"
 #include "../windows/InfoWindows.h"
 #include "../CServerHandler.h"
 
@@ -92,8 +92,14 @@ CMenuScreen::CMenuScreen(const JsonNode & configNode)
 	menuNameToEntry.push_back("credits");
 
 	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"]));
+	}
+	else
 		tabs->setRedrawParent(true);
+
 }
 
 std::shared_ptr<CIntObject> CMenuScreen::createTab(size_t index)
@@ -104,34 +110,12 @@ std::shared_ptr<CIntObject> CMenuScreen::createTab(size_t index)
 		return std::make_shared<CMenuEntry>(this, config["items"].Vector()[index]);
 }
 
-void CMenuScreen::show(Canvas & to)
-{
-	if(!config["video"].isNull())
-	{
-		// 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()
 {
 	CCS->musich->playMusic(AudioPath::builtin("Music/MainMenu"), true, true);
-	if(!config["video"].isNull())
-		CCS->videoh->open(VideoPath::fromJson(config["video"]["name"]));
 	CIntObject::activate();
 }
 
-void CMenuScreen::deactivate()
-{
-	if(!config["video"].isNull())
-		CCS->videoh->close();
-
-	CIntObject::deactivate();
-}
-
 void CMenuScreen::switchToTab(size_t index)
 {
 	tabs->setActive(index);

+ 2 - 3
client/mainmenu/CMainMenu.h

@@ -28,7 +28,7 @@ class CAnimation;
 class CButton;
 class CFilledTexture;
 class CLabel;
-
+class VideoWidget;
 
 // TODO: Find new location for these enums
 enum class ESelectionScreen : ui8 {
@@ -48,6 +48,7 @@ class CMenuScreen : public CWindowObject
 	std::shared_ptr<CTabbedInt> tabs;
 
 	std::shared_ptr<CPicture> background;
+	std::shared_ptr<VideoWidget> videoPlayer;
 	std::vector<std::shared_ptr<CPicture>> images;
 
 	std::shared_ptr<CIntObject> createTab(size_t index);
@@ -57,9 +58,7 @@ public:
 
 	CMenuScreen(const JsonNode & configNode);
 
-	void show(Canvas & to) override;
 	void activate() override;
-	void deactivate() override;
 
 	void switchToTab(size_t index);
 	void switchToTab(std::string name);

+ 8 - 7
client/mainmenu/CPrologEpilogVideo.cpp

@@ -14,14 +14,13 @@
 #include "../CGameInfo.h"
 #include "../media/IMusicPlayer.h"
 #include "../media/ISoundPlayer.h"
-#include "../media/IVideoPlayer.h"
 #include "../gui/WindowHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/FramerateManager.h"
 #include "../widgets/TextControls.h"
+#include "../widgets/VideoWidget.h"
 #include "../render/Canvas.h"
 
-
 CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::function<void()> callback)
 	: CWindowObject(BORDERED), spe(_spe), positionCounter(0), voiceSoundHandle(-1), videoSoundHandle(-1), exitCb(callback), elapsedTimeMilliseconds(0)
 {
@@ -30,9 +29,12 @@ CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::f
 	pos = center(Rect(0, 0, 800, 600));
 	updateShadow();
 
-	auto audioData = CCS->videoh->getAudio(spe.prologVideo);
-	videoSoundHandle = CCS->soundh->playSound(audioData, -1);
-	CCS->videoh->open(spe.prologVideo);
+	videoPlayer = std::make_shared<VideoWidget>(Point(30, 120), spe.prologVideo);
+
+	//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);
 	voiceDurationMilliseconds = CCS->soundh->getSoundDurationMilliseconds(spe.prologVoice);
 	voiceSoundHandle = CCS->soundh->playSound(spe.prologVoice);
@@ -66,9 +68,8 @@ void CPrologEpilogVideo::tick(uint32_t msPassed)
 void CPrologEpilogVideo::show(Canvas & to)
 {
 	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
 }
 

+ 2 - 0
client/mainmenu/CPrologEpilogVideo.h

@@ -13,6 +13,7 @@
 #include "../../lib/campaign/CampaignScenarioPrologEpilog.h"
 
 class CMultiLineLabel;
+class VideoWidget;
 
 class CPrologEpilogVideo : public CWindowObject
 {
@@ -25,6 +26,7 @@ class CPrologEpilogVideo : public CWindowObject
 	std::function<void()> exitCb;
 
 	std::shared_ptr<CMultiLineLabel> text;
+	std::shared_ptr<VideoWidget> videoPlayer;
 
 	bool voiceStopped = false;
 

+ 0 - 3
client/media/CEmptyVideoPlayer.h

@@ -15,13 +15,10 @@
 class CEmptyVideoPlayer final : public IVideoPlayer
 {
 public:
-	int curFrame() const override {return -1;};
-	int frameCount() const override {return -1;};
 	void redraw( int x, int y, SDL_Surface *dst, bool update) override {};
 	void show( int x, int y, SDL_Surface *dst, bool update) override {};
 	bool nextFrame() override {return false;};
 	void close() override {};
-	bool wait() override {return false;};
 	bool open(const VideoPath & name, bool scale) override {return false;};
 	void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update, std::function<void()> restart) override {}
 	bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType) override { return false; }

+ 0 - 5
client/media/CVideoHandler.h

@@ -73,11 +73,6 @@ public:
 
 	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;

+ 0 - 3
client/media/IVideoPlayer.h

@@ -31,9 +31,6 @@ public:
 	virtual bool nextFrame()=0;
 	virtual void show(int x, int y, SDL_Surface *dst, bool update = true)=0;
 	virtual void redraw(int x, int y, SDL_Surface *dst, bool update = true)=0; //reblits buffer
-	virtual bool wait()=0;
-	virtual int curFrame() const =0;
-	virtual int frameCount() const =0;
 	virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true, std::function<void()> restart = nullptr) = 0;
 	virtual bool openAndPlayVideo(const VideoPath & name, int x, int y, EVideoType videoType) = 0;
 	virtual std::pair<std::unique_ptr<ui8 []>, si64> getAudio(const VideoPath & videoToOpen) = 0;

+ 68 - 0
client/widgets/VideoWidget.cpp

@@ -0,0 +1,68 @@
+/*
+ * 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 "../media/IVideoPlayer.h"
+#include "../media/ISoundPlayer.h"
+#include "../render/Canvas.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/WindowHandler.h"
+
+VideoWidget::VideoWidget(const Point & position, const VideoPath & looped)
+	: VideoWidget(position, VideoPath(), looped)
+{
+}
+
+VideoWidget::VideoWidget(const Point & position, const VideoPath & prologue, const VideoPath & looped)
+	:current(prologue)
+	,next(looped)
+	,videoSoundHandle(-1)
+{
+}
+
+VideoWidget::~VideoWidget() = default;
+
+void VideoWidget::show(Canvas & to)
+{
+	CCS->videoh->update(pos.x + 107, pos.y + 70, to.getInternalSurface(), true, false);
+}
+
+void VideoWidget::activate()
+{
+	CCS->videoh->open(current);
+	auto audioData = CCS->videoh->getAudio(current);
+	videoSoundHandle = CCS->soundh->playSound(audioData, -1);
+
+	if (videoSoundHandle != -1)
+	{
+		CCS->soundh->setCallback(videoSoundHandle, [this](){
+			if (GH.windows().isTopWindow(this))
+				this->videoSoundHandle = -1;
+		});
+	}
+}
+
+void VideoWidget::deactivate()
+{
+	CCS->videoh->close();
+	CCS->soundh->stopSound(videoSoundHandle);
+}
+
+void VideoWidget::showAll(Canvas & to)
+{
+	CCS->videoh->update(pos.x + 107, pos.y + 70, to.getInternalSurface(), true, false);
+}
+
+void VideoWidget::tick(uint32_t msPassed)
+{
+
+}

+ 32 - 0
client/widgets/VideoWidget.h

@@ -0,0 +1,32 @@
+/*
+ * 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 VideoWidget final : public CIntObject
+{
+	VideoPath current;
+	VideoPath next;
+
+	int videoSoundHandle;
+public:
+	VideoWidget(const Point & position, const VideoPath & prologue, const VideoPath & looped);
+	VideoWidget(const Point & position, const VideoPath & looped);
+	~VideoWidget();
+
+	void activate() override;
+	void deactivate() override;
+	void show(Canvas & to) override;
+	void showAll(Canvas & to) override;
+	void tick(uint32_t msPassed) override;
+};

+ 5 - 25
client/windows/CTutorialWindow.cpp

@@ -20,10 +20,10 @@
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
 #include "../gui/WindowHandler.h"
-#include "../media/IVideoPlayer.h"
 #include "../widgets/Images.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/TextControls.h"
+#include "../widgets/VideoWidget.h"
 #include "../render/Canvas.h"
 
 CTutorialWindow::CTutorialWindow(const TutorialMode & m)
@@ -54,7 +54,10 @@ CTutorialWindow::CTutorialWindow(const TutorialMode & m)
 
 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);
 
 	buttonLeft->block(page<1);
 	buttonRight->block(page>videos.size() - 2);
@@ -98,26 +101,3 @@ void CTutorialWindow::previous()
 	deactivate();
 	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();
-}

+ 2 - 5
client/windows/CTutorialWindow.h

@@ -15,6 +15,7 @@ class CFilledTexture;
 class CButton;
 class CLabel;
 class CMultiLineLabel;
+class VideoWidget;
 
 enum TutorialMode
 {
@@ -33,8 +34,8 @@ class CTutorialWindow : public CWindowObject
 
 	std::shared_ptr<CLabel> labelTitle;
 	std::shared_ptr<CMultiLineLabel> labelInformation;
+	std::shared_ptr<VideoWidget> videoPlayer;
 
-	std::string video;
 	std::vector<std::string> videos;
 
 	int page;
@@ -47,8 +48,4 @@ class CTutorialWindow : public CWindowObject
 public:
 	CTutorialWindow(const TutorialMode & m);
 	static void openWindowFirstTime(const TutorialMode & m);	
-
-	void show(Canvas & to) override;
-	void activate() override;
-	void deactivate() override;
 };

+ 4 - 12
client/windows/GUIClasses.cpp

@@ -26,8 +26,6 @@
 #include "../gui/Shortcut.h"
 #include "../gui/WindowHandler.h"
 
-#include "../media/IVideoPlayer.h"
-
 #include "../widgets/CComponent.h"
 #include "../widgets/CGarrisonInt.h"
 #include "../widgets/CreatureCostBox.h"
@@ -36,6 +34,7 @@
 #include "../widgets/Slider.h"
 #include "../widgets/TextControls.h"
 #include "../widgets/ObjectLists.h"
+#include "../widgets/VideoWidget.h"
 
 #include "../render/Canvas.h"
 #include "../render/CAnimation.h"
@@ -516,11 +515,11 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
 			recruit->block(true);
 	}
 	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);
 	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);
 	else
-		CCS->videoh->open(VideoPath::builtin("TAVERN.BIK"));
+		videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), VideoPath::builtin("TAVERN.BIK"));
 
 	addInvite();
 }
@@ -573,11 +572,6 @@ void CTavernWindow::close()
 	CStatusbarWindow::close();
 }
 
-CTavernWindow::~CTavernWindow()
-{
-	CCS->videoh->close();
-}
-
 void CTavernWindow::show(Canvas & to)
 {
 	CWindowObject::show(to);
@@ -601,8 +595,6 @@ void CTavernWindow::show(Canvas & to)
 
 		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)

+ 2 - 1
client/windows/GUIClasses.h

@@ -39,6 +39,7 @@ class CHeroArea;
 class CAnimImage;
 class CFilledTexture;
 class IImage;
+class VideoWidget;
 
 enum class EUserEvent;
 
@@ -261,6 +262,7 @@ public:
 	std::shared_ptr<CLabel> cost;
 	std::shared_ptr<CLabel> heroesForHire;
 	std::shared_ptr<CTextBox> heroDescription;
+	std::shared_ptr<VideoWidget> videoPlayer;
 
 	std::shared_ptr<CTextBox> rumor;
 	
@@ -272,7 +274,6 @@ public:
 	void addInvite();
 
 	CTavernWindow(const CGObjectInstance * TavernObj, const std::function<void()> & onWindowClosed);
-	~CTavernWindow();
 
 	void close() override;
 	void recruitb();