From c4eb1b8b970e38188a64f20567f4c3568930660d Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Sat, 19 Feb 2022 17:04:23 +0100
Subject: [PATCH] Greatly simplified the video controls code.

Also fixed a cosmetic issue with carousel fade transitions.
---
 es-app/src/FileData.cpp                       |   2 +-
 es-app/src/MediaViewer.cpp                    |  10 +-
 es-app/src/Screensaver.cpp                    |   8 +-
 es-app/src/guis/GuiGamelistOptions.cpp        |   2 +
 es-app/src/guis/GuiMenu.cpp                   |   2 +
 es-app/src/guis/GuiMetaDataEd.cpp             |   2 -
 es-app/src/guis/GuiScraperMulti.cpp           |   1 -
 es-app/src/scrapers/Scraper.cpp               |   2 +-
 es-app/src/views/GamelistBase.cpp             |  13 +-
 es-app/src/views/GamelistLegacy.h             |  27 +-
 es-app/src/views/GamelistView.cpp             |  43 ++-
 es-app/src/views/GamelistView.h               |  22 ++
 es-app/src/views/ViewController.cpp           |  16 +-
 es-app/src/views/ViewController.h             |   6 +
 es-core/src/GuiComponent.cpp                  |  48 ----
 es-core/src/GuiComponent.h                    |  17 +-
 es-core/src/Window.cpp                        |  42 +--
 es-core/src/Window.h                          |   7 +-
 es-core/src/components/VideoComponent.cpp     | 267 ++++--------------
 es-core/src/components/VideoComponent.h       |  58 ++--
 .../src/components/VideoFFmpegComponent.cpp   |  80 +++---
 es-core/src/components/VideoFFmpegComponent.h |  20 +-
 22 files changed, 229 insertions(+), 466 deletions(-)

diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp
index 2af22e8e4..5e7d6a604 100644
--- a/es-app/src/FileData.cpp
+++ b/es-app/src/FileData.cpp
@@ -1196,7 +1196,7 @@ void FileData::launchGame()
         // This blocks the video player, stops the scrolling of game names and descriptions and
         // keeps the screensaver from getting activated.
         if (runInBackground)
-            window->setLaunchedGame();
+            window->setLaunchedGame(true);
         // Normalize deltaTime so that the screensaver does not start immediately
         // when returning from the game.
         window->normalizeNextUpdate();
diff --git a/es-app/src/MediaViewer.cpp b/es-app/src/MediaViewer.cpp
index ca56f5b66..08592341d 100644
--- a/es-app/src/MediaViewer.cpp
+++ b/es-app/src/MediaViewer.cpp
@@ -43,9 +43,6 @@ bool MediaViewer::startMediaViewer(FileData* game)
 
     initiateViewer();
 
-    if (mHasVideo)
-        ViewController::getInstance()->onPauseVideo();
-
     if (mHasVideo || mHasImages)
         return true;
     else
@@ -55,7 +52,7 @@ bool MediaViewer::startMediaViewer(FileData* game)
 void MediaViewer::stopMediaViewer()
 {
     NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
-    ViewController::getInstance()->onStopVideo();
+    ViewController::getInstance()->stopViewVideos();
 
     if (mVideo) {
         delete mVideo;
@@ -257,10 +254,9 @@ void MediaViewer::playVideo()
         return;
 
     mDisplayingImage = false;
-    ViewController::getInstance()->onStopVideo();
+    ViewController::getInstance()->pauseViewVideos();
 
     mVideo = new VideoFFmpegComponent;
-    mVideo->topWindow(true);
     mVideo->setOrigin(0.5f, 0.5f);
     mVideo->setPosition(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f);
 
@@ -271,7 +267,7 @@ void MediaViewer::playVideo()
 
     mVideo->setVideo(mVideoFile);
     mVideo->setMediaViewerMode(true);
-    mVideo->onShow();
+    mVideo->startVideoPlayer();
 }
 
 void MediaViewer::showImage(int index)
diff --git a/es-app/src/Screensaver.cpp b/es-app/src/Screensaver.cpp
index 6dca52057..439f89fae 100644
--- a/es-app/src/Screensaver.cpp
+++ b/es-app/src/Screensaver.cpp
@@ -59,6 +59,8 @@ Screensaver::~Screensaver()
 
 void Screensaver::startScreensaver(bool generateMediaList)
 {
+    ViewController::getInstance()->pauseViewVideos();
+
     std::string path = "";
     std::string screensaverType = Settings::getInstance()->getString("ScreensaverType");
     mHasMediaFiles = false;
@@ -157,7 +159,6 @@ void Screensaver::startScreensaver(bool generateMediaList)
                 generateOverlayInfo();
 
             mVideoScreensaver = new VideoFFmpegComponent;
-            mVideoScreensaver->topWindow(true);
             mVideoScreensaver->setOrigin(0.5f, 0.5f);
             mVideoScreensaver->setPosition(Renderer::getScreenWidth() / 2.0f,
                                            Renderer::getScreenHeight() / 2.0f);
@@ -171,7 +172,7 @@ void Screensaver::startScreensaver(bool generateMediaList)
 
             mVideoScreensaver->setVideo(path);
             mVideoScreensaver->setScreensaverMode(true);
-            mVideoScreensaver->onShow();
+            mVideoScreensaver->startVideoPlayer();
             mTimer = 0;
             return;
         }
@@ -197,6 +198,8 @@ void Screensaver::stopScreensaver()
 
     if (mGameOverlay)
         mGameOverlay.reset();
+
+    ViewController::getInstance()->startViewVideos();
 }
 
 void Screensaver::nextGame()
@@ -215,6 +218,7 @@ void Screensaver::launchGame()
             ViewController::getInstance()->getGamelistView(mCurrentGame->getSystem()).get();
         view->setCursor(mCurrentGame);
         ViewController::getInstance()->cancelViewTransitions();
+        ViewController::getInstance()->pauseViewVideos();
     }
 }
 
diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp
index 5761cc20a..06469887a 100644
--- a/es-app/src/guis/GuiGamelistOptions.cpp
+++ b/es-app/src/guis/GuiGamelistOptions.cpp
@@ -254,6 +254,8 @@ GuiGamelistOptions::~GuiGamelistOptions()
     // the menu has been closed.
     ViewController::getInstance()->stopScrolling();
 
+    ViewController::getInstance()->startViewVideos();
+
     if (mFiltersChanged) {
         if (!mCustomCollectionSystem) {
             ViewController::getInstance()->reloadGamelistView(mSystem);
diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp
index 584ceb441..5839a6032 100644
--- a/es-app/src/guis/GuiMenu.cpp
+++ b/es-app/src/guis/GuiMenu.cpp
@@ -87,6 +87,8 @@ GuiMenu::~GuiMenu()
     // was openened. Without this, the scrolling would run until manually stopped after
     // the menu has been closed.
     ViewController::getInstance()->stopScrolling();
+
+    ViewController::getInstance()->startViewVideos();
 }
 
 void GuiMenu::openScraperOptions()
diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp
index 950a0a3a0..94b3e93ec 100644
--- a/es-app/src/guis/GuiMetaDataEd.cpp
+++ b/es-app/src/guis/GuiMetaDataEd.cpp
@@ -514,7 +514,6 @@ GuiMetaDataEd::GuiMetaDataEd(MetaDataList* md,
 
     buttons.push_back(std::make_shared<ButtonComponent>("SAVE", "save metadata", [&] {
         save();
-        ViewController::getInstance()->onPauseVideo();
         delete this;
     }));
     buttons.push_back(
@@ -841,7 +840,6 @@ void GuiMetaDataEd::close()
             CollectionSystemsManager::getInstance()->refreshCollectionSystems(mScraperParams.game);
             mWindow->invalidateCachedBackground();
         }
-        ViewController::getInstance()->onPauseVideo();
         delete this;
     };
 
diff --git a/es-app/src/guis/GuiScraperMulti.cpp b/es-app/src/guis/GuiScraperMulti.cpp
index 10713d660..4b9f50d7c 100644
--- a/es-app/src/guis/GuiScraperMulti.cpp
+++ b/es-app/src/guis/GuiScraperMulti.cpp
@@ -175,7 +175,6 @@ GuiScraperMulti::~GuiScraperMulti()
             (*it)->sortSystem();
         }
     }
-    ViewController::getInstance()->onPauseVideo();
 }
 
 void GuiScraperMulti::onSizeChanged()
diff --git a/es-app/src/scrapers/Scraper.cpp b/es-app/src/scrapers/Scraper.cpp
index 6a42da6d3..b50fa9b82 100644
--- a/es-app/src/scrapers/Scraper.cpp
+++ b/es-app/src/scrapers/Scraper.cpp
@@ -261,7 +261,7 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result,
         scrapeFiles.push_back(mediaFileInfo);
 #if defined(_WIN64)
         // Required due to the idiotic file locking that exists on this operating system.
-        ViewController::getInstance()->onStopVideo();
+        ViewController::getInstance()->stopVideo();
 #endif
     }
 
diff --git a/es-app/src/views/GamelistBase.cpp b/es-app/src/views/GamelistBase.cpp
index bb759992a..6d53aba9e 100644
--- a/es-app/src/views/GamelistBase.cpp
+++ b/es-app/src/views/GamelistBase.cpp
@@ -74,7 +74,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
         if (config->isMappedTo("a", input)) {
             FileData* cursor = getCursor();
             if (cursor->getType() == GAME) {
-                onPauseVideo();
+                pauseViewVideos();
                 ViewController::getInstance()->cancelViewTransitions();
                 stopListScrolling();
                 launch(cursor);
@@ -131,7 +131,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
             }
             else {
                 NavigationSounds::getInstance().playThemeNavigationSound(BACKSOUND);
-                onPauseVideo();
+                muteViewVideos();
                 onFocusLost();
                 stopListScrolling();
                 SystemData* systemToView = getCursor()->getSystem();
@@ -175,7 +175,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
         else if (config->isMappedLike(getQuickSystemSelectRightButton(), input)) {
             if (Settings::getInstance()->getBool("QuickSystemSelect") &&
                 SystemData::sSystemVector.size() > 1) {
-                onPauseVideo();
+                muteViewVideos();
                 onFocusLost();
                 stopListScrolling();
                 ViewController::getInstance()->goToNextGamelist();
@@ -185,7 +185,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
         else if (config->isMappedLike(getQuickSystemSelectLeftButton(), input)) {
             if (Settings::getInstance()->getBool("QuickSystemSelect") &&
                 SystemData::sSystemVector.size() > 1) {
-                onPauseVideo();
+                muteViewVideos();
                 onFocusLost();
                 stopListScrolling();
                 ViewController::getInstance()->goToPrevGamelist();
@@ -457,6 +457,7 @@ bool GamelistBase::input(InputConfig* config, Input input)
         config->isMappedTo("back", input) && input.value) {
         ViewController::getInstance()->cancelViewTransitions();
         stopListScrolling();
+        pauseViewVideos();
         mWindow->pushGui(new GuiGamelistOptions(this->mRoot->getSystem()));
         return true;
     }
@@ -621,7 +622,7 @@ void GamelistBase::generateFirstLetterIndex(const std::vector<FileData*>& files)
 void GamelistBase::generateGamelistInfo(FileData* cursor, FileData* firstEntry)
 {
     // Generate data needed for the gamelistInfo field, which is displayed from the
-    // gamelist interfaces (Detailed/Video/Grid).
+    // gamelist interfaces.
     mIsFiltered = false;
     mIsFolder = false;
     FileData* rootFolder {firstEntry->getSystem()->getRootFolder()};
@@ -704,7 +705,7 @@ void GamelistBase::removeMedia(FileData* game)
     std::string path;
 
     // Stop the video player, especially important on Windows as the file would otherwise be locked.
-    onStopVideo();
+    stopViewVideos();
 
     // If there are no media files left in the directory after the deletion, then remove
     // the directory too. Remove any empty parent directories as well.
diff --git a/es-app/src/views/GamelistLegacy.h b/es-app/src/views/GamelistLegacy.h
index bc68bdd7d..b77206d3e 100644
--- a/es-app/src/views/GamelistLegacy.h
+++ b/es-app/src/views/GamelistLegacy.h
@@ -366,6 +366,12 @@ void GamelistView::legacyUpdateInfoPanel()
 
     bool fadingOut = false;
     if (file == nullptr) {
+        if (mViewStyle == ViewController::VIDEO) {
+            mVideoComponents.front()->stopVideoPlayer();
+            mVideoComponents.front()->setVideo("");
+            if (!mVideoComponents.front()->hasStartDelay())
+                mVideoComponents.front()->setImage("");
+        }
         mVideoPlaying = false;
         fadingOut = true;
     }
@@ -383,15 +389,12 @@ void GamelistView::legacyUpdateInfoPanel()
                 mImageComponents[LegacyImage::MD_MARQUEE]->setImage(mRandomGame->getMarqueePath());
                 if (mViewStyle == ViewController::VIDEO) {
                     mVideoComponents.front()->setImage(mRandomGame->getImagePath());
-                    // Always stop the video before setting a new video as it will otherwise
-                    // continue to play if it has the same path (i.e. it is the same physical
-                    // video file) as the previously set video. That may happen when entering a
-                    // folder with the same name as the first game file inside, or as in this
-                    // case, when entering a custom collection.
-                    mVideoComponents.front()->onHide();
+                    mVideoComponents.front()->stopVideoPlayer();
 
                     if (!mVideoComponents.front()->setVideo(mRandomGame->getVideoPath()))
                         mVideoComponents.front()->setDefaultVideo();
+
+                    mVideoComponents.front()->startVideoPlayer();
                 }
                 else {
                     mImageComponents[LegacyImage::MD_IMAGE]->setImage(mRandomGame->getImagePath());
@@ -413,10 +416,12 @@ void GamelistView::legacyUpdateInfoPanel()
             mImageComponents[LegacyImage::MD_MARQUEE]->setImage(file->getMarqueePath());
             if (mViewStyle == ViewController::VIDEO) {
                 mVideoComponents.front()->setImage(file->getImagePath());
-                mVideoComponents.front()->onHide();
+                mVideoComponents.front()->stopVideoPlayer();
 
                 if (!mVideoComponents.front()->setVideo(file->getVideoPath()))
                     mVideoComponents.front()->setDefaultVideo();
+
+                mVideoComponents.front()->startVideoPlayer();
             }
             else {
                 mImageComponents[LegacyImage::MD_IMAGE]->setImage(file->getImagePath());
@@ -550,6 +555,8 @@ void GamelistView::legacyUpdateInfoPanel()
     comps.emplace_back(mImageComponents[LegacyImage::MD_THUMBNAIL].get());
     comps.emplace_back(mImageComponents[LegacyImage::MD_MARQUEE].get());
     comps.emplace_back(mImageComponents[LegacyImage::MD_IMAGE].get());
+    if (mVideoComponents.size() > 0)
+        comps.emplace_back(mVideoComponents.front().get());
     comps.push_back(mBadgeComponents.front().get());
     comps.push_back(mRatingComponents.front().get());
 
@@ -576,12 +583,6 @@ void GamelistView::legacyUpdate(int deltaTime)
         mImageComponents[LegacyImage::MD_IMAGE]->finishAnimation(0);
 
     if (mViewStyle == ViewController::VIDEO) {
-        if (!mVideoPlaying)
-            mVideoComponents.front()->onHide();
-        else if (mVideoPlaying && !mVideoComponents.front()->isVideoPaused() &&
-                 !mWindow->isScreensaverActive())
-            mVideoComponents.front()->onShow();
-
         if (ViewController::getInstance()->getGameLaunchTriggered() &&
             mVideoComponents.front()->isAnimationPlaying(0))
             mVideoComponents.front()->finishAnimation(0);
diff --git a/es-app/src/views/GamelistView.cpp b/es-app/src/views/GamelistView.cpp
index 8dba275ca..c168dab01 100644
--- a/es-app/src/views/GamelistView.cpp
+++ b/es-app/src/views/GamelistView.cpp
@@ -204,23 +204,6 @@ void GamelistView::update(int deltaTime)
         }
     }
 
-    for (auto& video : mVideoComponents) {
-        if (!mVideoPlaying) {
-            if (!video->getScrollHide())
-                video->onHide();
-            else if (!video->hasStaticImage())
-                video->onHide();
-            else if (video->getOpacity() == 0.0f)
-                video->onHide();
-        }
-        else if (mVideoPlaying && !video->isVideoPaused() && !mWindow->isScreensaverActive()) {
-            video->onShow();
-        }
-
-        if (ViewController::getInstance()->getGameLaunchTriggered() && video->isAnimationPlaying(0))
-            video->finishAnimation(0);
-    }
-
     updateChildren(deltaTime);
 }
 
@@ -375,6 +358,14 @@ void GamelistView::updateInfoPanel()
 
     bool fadingOut = false;
     if (file == nullptr) {
+        if (mVideoPlaying) {
+            for (auto& video : mVideoComponents) {
+                video->stopVideoPlayer();
+                video->setVideo("");
+                if (!video->hasStartDelay())
+                    video->setImage("");
+            }
+        }
         mVideoPlaying = false;
         fadingOut = true;
     }
@@ -393,17 +384,14 @@ void GamelistView::updateInfoPanel()
                 for (auto& video : mVideoComponents) {
                     setGameImage(mRandomGame, video.get());
 
-                    // Always stop the video before setting a new video as it will otherwise
-                    // continue to play if it has the same path (i.e. it is the same physical
-                    // video file) as the previously set video. That may happen when entering a
-                    // folder with the same name as the first game file inside, or as in this
-                    // case, when entering a custom collection.
-                    video->onHide();
+                    video->stopVideoPlayer();
 
                     if (video->hasStaticVideo())
                         video->setStaticVideo();
                     else if (!video->setVideo(mRandomGame->getVideoPath()))
                         video->setDefaultVideo();
+
+                    video->startVideoPlayer();
                 }
             }
             else {
@@ -413,10 +401,10 @@ void GamelistView::updateInfoPanel()
                 }
 
                 for (auto& video : mVideoComponents) {
+                    video->stopVideoPlayer();
                     video->setImage("");
                     video->setVideo("");
                     if (video->hasStaticVideo()) {
-                        video->onStopVideo();
                         video->setStaticVideo();
                     }
                     else {
@@ -426,17 +414,20 @@ void GamelistView::updateInfoPanel()
             }
         }
         else {
-            for (auto& image : mImageComponents)
+            for (auto& image : mImageComponents) {
                 setGameImage(file, image.get());
+            }
 
             for (auto& video : mVideoComponents) {
                 setGameImage(file, video.get());
-                video->onHide();
+                video->stopVideoPlayer();
 
                 if (video->hasStaticVideo())
                     video->setStaticVideo();
                 else if (!video->setVideo(file->getVideoPath()))
                     video->setDefaultVideo();
+
+                video->startVideoPlayer();
             }
         }
 
diff --git a/es-app/src/views/GamelistView.h b/es-app/src/views/GamelistView.h
index fb9304070..4f9ae35f0 100644
--- a/es-app/src/views/GamelistView.h
+++ b/es-app/src/views/GamelistView.h
@@ -41,6 +41,28 @@ public:
         }
     }
 
+    void startViewVideos() override
+    {
+        for (auto& video : mVideoComponents)
+            video->startVideoPlayer();
+    }
+    void stopViewVideos() override
+    {
+        for (auto& video : mVideoComponents)
+            video->stopVideoPlayer();
+    }
+    void pauseViewVideos() override
+    {
+        for (auto& video : mVideoComponents) {
+            video->pauseVideoPlayer();
+        }
+    }
+    void muteViewVideos() override
+    {
+        for (auto& video : mVideoComponents)
+            video->muteVideoPlayer();
+    }
+
     const std::shared_ptr<ThemeData> getTheme() const { return mTheme; }
     void setTheme(const std::shared_ptr<ThemeData>& theme)
     {
diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp
index 846027d04..64ad24b9c 100644
--- a/es-app/src/views/ViewController.cpp
+++ b/es-app/src/views/ViewController.cpp
@@ -648,11 +648,6 @@ void ViewController::launch(FileData* game)
         return;
     }
 
-    // If the video view style is used, pause the video currently playing or block the
-    // video from starting to play if the static image is still shown.
-    if (mCurrentView)
-        mCurrentView->onPauseVideo();
-
     // Disable text scrolling and stop any Lottie animations. These will be enabled again in
     // FileData upon returning from the game.
     mWindow->setAllowTextScrolling(false);
@@ -806,7 +801,7 @@ bool ViewController::input(InputConfig* config, Input input)
     if (mWindow->getGameLaunchedState()) {
         mWindow->setAllowTextScrolling(true);
         mWindow->setAllowFileAnimation(true);
-        mWindow->unsetLaunchedGame();
+        mWindow->setLaunchedGame(false);
         // Filter out the "a" button so the game is not restarted if there was such a button press
         // queued when leaving the game.
         if (config->isMappedTo("a", input) && input.value != 0)
@@ -825,9 +820,12 @@ bool ViewController::input(InputConfig* config, Input input)
         // to play when we've closed the menu.
         if (mSystemListView->isSystemAnimationPlaying(0))
             mSystemListView->finishSystemAnimation(0);
-        // Stop the gamelist scrolling as well as it would otherwise
-        // also continue to run after closing the menu.
+        // Stop the gamelist scrolling as well as it would otherwise continue to run after
+        // closing the menu.
         mCurrentView->stopListScrolling();
+        // Pause all videos as they would otherwise continue to play beneath the menu.
+        mCurrentView->pauseViewVideos();
+
         // Finally, if the camera is currently moving, reset its position.
         cancelViewTransitions();
 
@@ -985,7 +983,7 @@ void ViewController::reloadGamelistView(GamelistView* view, bool reloadTheme)
     // video player, prevent scrolling of game names and game descriptions and prevent the
     // screensaver from starting on schedule.
     if (mWindow->getGameLaunchedState())
-        mWindow->setLaunchedGame();
+        mWindow->setLaunchedGame(true);
 
     // Redisplay the current view.
     if (mCurrentView)
diff --git a/es-app/src/views/ViewController.h b/es-app/src/views/ViewController.h
index 1ce4dd95b..eb589ab80 100644
--- a/es-app/src/views/ViewController.h
+++ b/es-app/src/views/ViewController.h
@@ -66,6 +66,12 @@ public:
     void cancelViewTransitions();
     void stopScrolling();
 
+    // Basic video controls.
+    void startViewVideos() override { mCurrentView->startViewVideos(); }
+    void stopViewVideos() override { mCurrentView->stopViewVideos(); }
+    void pauseViewVideos() override { mCurrentView->pauseViewVideos(); }
+    void muteViewVideos() override { mCurrentView->muteViewVideos(); }
+
     void onFileChanged(FileData* file, bool reloadGamelist);
     void triggerGameLaunch(FileData* game)
     {
diff --git a/es-core/src/GuiComponent.cpp b/es-core/src/GuiComponent.cpp
index f0051ea7f..21e6dda22 100644
--- a/es-core/src/GuiComponent.cpp
+++ b/es-core/src/GuiComponent.cpp
@@ -393,51 +393,3 @@ void GuiComponent::onHide()
     for (unsigned int i = 0; i < getChildCount(); ++i)
         getChild(i)->onHide();
 }
-
-void GuiComponent::onStopVideo()
-{
-    for (unsigned int i = 0; i < getChildCount(); ++i)
-        getChild(i)->onStopVideo();
-}
-
-void GuiComponent::onPauseVideo()
-{
-    for (unsigned int i = 0; i < getChildCount(); ++i)
-        getChild(i)->onPauseVideo();
-}
-
-void GuiComponent::onUnpauseVideo()
-{
-    for (unsigned int i = 0; i < getChildCount(); ++i)
-        getChild(i)->onUnpauseVideo();
-}
-
-void GuiComponent::onScreensaverActivate()
-{
-    for (unsigned int i = 0; i < getChildCount(); ++i)
-        getChild(i)->onScreensaverActivate();
-}
-
-void GuiComponent::onScreensaverDeactivate()
-{
-    for (unsigned int i = 0; i < getChildCount(); ++i)
-        getChild(i)->onScreensaverDeactivate();
-}
-
-void GuiComponent::onGameLaunchedActivate()
-{
-    for (unsigned int i = 0; i < getChildCount(); ++i)
-        getChild(i)->onGameLaunchedActivate();
-}
-
-void GuiComponent::onGameLaunchedDeactivate()
-{
-    for (unsigned int i = 0; i < getChildCount(); ++i)
-        getChild(i)->onGameLaunchedDeactivate();
-}
-
-void GuiComponent::topWindow(bool isTop)
-{
-    for (unsigned int i = 0; i < getChildCount(); ++i)
-        getChild(i)->topWindow(isTop);
-}
diff --git a/es-core/src/GuiComponent.h b/es-core/src/GuiComponent.h
index 8cb03502e..9e2052a70 100644
--- a/es-core/src/GuiComponent.h
+++ b/es-core/src/GuiComponent.h
@@ -238,19 +238,16 @@ public:
 
     virtual void onShow();
     virtual void onHide();
-    virtual void onStopVideo();
-    virtual void onPauseVideo();
-    virtual void onUnpauseVideo();
-    virtual bool isVideoPaused() { return false; }
+
+    // System view and gamelist view video controls.
+    virtual void startViewVideos() {}
+    virtual void stopViewVideos() {}
+    virtual void pauseViewVideos() {}
+    virtual void muteViewVideos() {}
+
     // For Lottie animations.
     virtual void resetFileAnimation() {};
 
-    virtual void onScreensaverActivate();
-    virtual void onScreensaverDeactivate();
-    virtual void onGameLaunchedActivate();
-    virtual void onGameLaunchedDeactivate();
-    virtual void topWindow(bool isTop);
-
     // Default implementation just handles <pos> and <size> tags as normalized float pairs.
     // You probably want to keep this behavior for any derived classes as well as add your own.
     virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,
diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp
index b2e91f680..2bf71f6df 100644
--- a/es-core/src/Window.cpp
+++ b/es-core/src/Window.cpp
@@ -75,10 +75,6 @@ Window* Window::getInstance()
 
 void Window::pushGui(GuiComponent* gui)
 {
-    if (mGuiStack.size() > 0) {
-        auto& top = mGuiStack.back();
-        top->topWindow(false);
-    }
     mGuiStack.push_back(gui);
     gui->updateHelpPrompts();
 }
@@ -90,10 +86,8 @@ void Window::removeGui(GuiComponent* gui)
             it = mGuiStack.erase(it);
 
             // We just popped the stack and the stack is not empty.
-            if (it == mGuiStack.cend() && mGuiStack.size()) {
+            if (it == mGuiStack.cend() && mGuiStack.size())
                 mGuiStack.back()->updateHelpPrompts();
-                mGuiStack.back()->topWindow(true);
-            }
 
             return;
         }
@@ -752,10 +746,6 @@ void Window::stopInfoPopup()
 void Window::startScreensaver()
 {
     if (mScreensaver && !mRenderScreensaver) {
-        // Tell the GUI components the screensaver is starting.
-        for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); ++it)
-            (*it)->onScreensaverActivate();
-
         setAllowTextScrolling(false);
         setAllowFileAnimation(false);
         mScreensaver->startScreensaver(true);
@@ -771,14 +761,6 @@ bool Window::stopScreensaver()
         setAllowTextScrolling(true);
         setAllowFileAnimation(true);
 
-        // Tell the GUI components the screensaver has stopped.
-        for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); ++it) {
-            (*it)->onScreensaverDeactivate();
-            // If the menu is open, pause the video so it won't start playing beneath the menu.
-            if (mGuiStack.front() != mGuiStack.back())
-                (*it)->onPauseVideo();
-        }
-
         return true;
     }
 
@@ -830,10 +812,6 @@ void Window::closeLaunchScreen()
     mRenderLaunchScreen = false;
 }
 
-void Window::increaseVideoPlayerCount() { ++mVideoPlayerCount; }
-
-void Window::decreaseVideoPlayerCount() { --mVideoPlayerCount; }
-
 int Window::getVideoPlayerCount()
 {
     int videoPlayerCount;
@@ -841,24 +819,6 @@ int Window::getVideoPlayerCount()
     return videoPlayerCount;
 }
 
-void Window::setLaunchedGame()
-{
-    // Tell the GUI components that a game has been launched.
-    for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); ++it)
-        (*it)->onGameLaunchedActivate();
-
-    mGameLaunchedState = true;
-}
-
-void Window::unsetLaunchedGame()
-{
-    // Tell the GUI components that the user is back in ES-DE again.
-    for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); ++it)
-        (*it)->onGameLaunchedDeactivate();
-
-    mGameLaunchedState = false;
-}
-
 void Window::invalidateCachedBackground()
 {
     mCachedBackground = false;
diff --git a/es-core/src/Window.h b/es-core/src/Window.h
index 8f9d12c83..82ec2263c 100644
--- a/es-core/src/Window.h
+++ b/es-core/src/Window.h
@@ -130,12 +130,11 @@ public:
     void setLaunchScreen(GuiLaunchScreen* launchScreen) { mLaunchScreen = launchScreen; }
     bool isLaunchScreenDisplayed() { return mRenderLaunchScreen; }
 
-    void increaseVideoPlayerCount();
-    void decreaseVideoPlayerCount();
+    void increaseVideoPlayerCount() { ++mVideoPlayerCount; }
+    void decreaseVideoPlayerCount() { --mVideoPlayerCount; }
     int getVideoPlayerCount();
 
-    void setLaunchedGame();
-    void unsetLaunchedGame();
+    void setLaunchedGame(bool state) { mGameLaunchedState = state; }
     void invalidateCachedBackground();
     bool isInvalidatingCachedBackground() { return mInvalidateCacheTimer > 0; }
 
diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp
index 13fa9e0b8..39f9cdf54 100644
--- a/es-core/src/components/VideoComponent.cpp
+++ b/es-core/src/components/VideoComponent.cpp
@@ -25,43 +25,36 @@ VideoComponent::VideoComponent()
     , mTargetSize {0.0f, 0.0f}
     , mVideoAreaPos {0.0f, 0.0f}
     , mVideoAreaSize {0.0f, 0.0f}
-    , mStartDelayed {false}
+    , mStartTime {0}
     , mIsPlaying {false}
     , mIsActuallyPlaying {false}
-    , mPause {false}
-    , mShowing {false}
-    , mDisable {false}
+    , mPaused {false}
     , mMediaViewerMode {false}
-    , mScreensaverActive {false}
     , mScreensaverMode {false}
-    , mGameLaunched {false}
-    , mBlockPlayer {false}
     , mTargetIsMax {false}
     , mDrawPillarboxes {true}
     , mRenderScanlines {false}
     , mLegacyTheme {false}
+    , mHasVideo {false}
     , mFadeIn {1.0f}
     , mFadeInTime {1000.0f}
 {
-    // Setup the default configuration.
+    // Setup default configuration.
     mConfig.showSnapshotDelay = false;
     mConfig.showSnapshotNoVideo = false;
     mConfig.startDelay = 1500;
-
-    if (mWindow->getGuiStackSize() > 1)
-        topWindow(false);
 }
 
 VideoComponent::~VideoComponent()
 {
     // Stop any currently running video.
-    stopVideo();
+    stopVideoPlayer();
 }
 
 bool VideoComponent::setVideo(std::string path)
 {
     // Convert the path into a generic format.
-    std::string fullPath = Utils::FileSystem::getCanonicalPath(path);
+    std::string fullPath {Utils::FileSystem::getCanonicalPath(path)};
 
     // Check that it's changed.
     if (fullPath == mVideoPath)
@@ -72,10 +65,17 @@ bool VideoComponent::setVideo(std::string path)
 
     // If the file exists then set the new video.
     if (!fullPath.empty() && ResourceManager::getInstance().fileExists(fullPath)) {
+        mHasVideo = true;
         // Return true to show that we are going to attempt to play a video.
         return true;
     }
 
+    if (!mVideoPath.empty() || !mConfig.defaultVideoPath.empty() ||
+        !mConfig.staticVideoPath.empty())
+        mHasVideo = true;
+    else
+        mHasVideo = false;
+
     // Return false to show that no video will be displayed.
     return false;
 }
@@ -87,7 +87,7 @@ void VideoComponent::setImage(const std::string& path, bool tile)
     if (imagePath == "")
         imagePath = mDefaultImagePath;
 
-    // Check that the image has changed.
+    // Check if the image has changed.
     if (imagePath == mStaticImagePath)
         return;
 
@@ -95,127 +95,6 @@ void VideoComponent::setImage(const std::string& path, bool tile)
     mStaticImagePath = imagePath;
 }
 
-void VideoComponent::onShow()
-{
-    mBlockPlayer = false;
-    mPause = false;
-    mShowing = true;
-    manageState();
-}
-
-void VideoComponent::onHide()
-{
-    mShowing = false;
-    manageState();
-}
-
-void VideoComponent::onStopVideo()
-{
-    stopVideo();
-    manageState();
-}
-
-void VideoComponent::onPauseVideo()
-{
-    mBlockPlayer = true;
-    mPause = true;
-    manageState();
-}
-
-void VideoComponent::onUnpauseVideo()
-{
-    mBlockPlayer = false;
-    mPause = false;
-    manageState();
-}
-
-void VideoComponent::onScreensaverActivate()
-{
-    mBlockPlayer = true;
-    mPause = true;
-
-    if (Settings::getInstance()->getString("ScreensaverType") == "dim")
-        stopVideo();
-    else
-        pauseVideo();
-    manageState();
-}
-
-void VideoComponent::onScreensaverDeactivate()
-{
-    mBlockPlayer = false;
-    // Stop video when deactivating the screensaver to force a reload of the
-    // static image (if the theme is configured as such).
-    stopVideo();
-    manageState();
-}
-
-void VideoComponent::onGameLaunchedActivate()
-{
-    mGameLaunched = true;
-    manageState();
-}
-
-void VideoComponent::onGameLaunchedDeactivate()
-{
-    mGameLaunched = false;
-    stopVideo();
-    manageState();
-}
-
-void VideoComponent::topWindow(bool isTop)
-{
-    if (isTop) {
-        mBlockPlayer = false;
-        mPause = false;
-
-        // Stop video when closing the menu to force a reload of the
-        // static image (if the theme is configured as such).
-        stopVideo();
-    }
-    else {
-        mBlockPlayer = true;
-        mPause = true;
-    }
-    manageState();
-}
-
-void VideoComponent::render(const glm::mat4& parentTrans)
-{
-    if (!isVisible())
-        return;
-
-    glm::mat4 trans {parentTrans * getTransform()};
-    GuiComponent::renderChildren(trans);
-
-    Renderer::setMatrix(trans);
-
-    // Handle the case where the video is delayed.
-    handleStartDelay();
-
-    // Handle looping of the video.
-    handleLooping();
-
-    // Pause video in case a game has been launched.
-    pauseVideo();
-}
-
-void VideoComponent::renderSnapshot(const glm::mat4& parentTrans)
-{
-    // This function is called when the video is not currently being played. We need to
-    // work out if we should display a static image. If the menu is open, then always render
-    // the static image as the metadata may have been changed. In that case the gamelist
-    // was reloaded and there would just be a blank space unless we render the image here.
-    // The side effect of this is that a static image is displayed even for themes that are
-    // set to start playing the video immediately. Although this may seem a bit inconsistent it
-    // simply looks better than leaving an empty space where the video would have been located.
-    if (mWindow->getGuiStackSize() > 1 || (mConfig.showSnapshotNoVideo && mVideoPath.empty()) ||
-        (mStartDelayed && mConfig.showSnapshotDelay)) {
-        mStaticImage.setOpacity(mOpacity * mThemeOpacity);
-        mStaticImage.render(parentTrans);
-    }
-}
-
 void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
                                 const std::string& view,
                                 const std::string& element,
@@ -327,12 +206,29 @@ std::vector<HelpPrompt> VideoComponent::getHelpPrompts()
 
 void VideoComponent::update(int deltaTime)
 {
-    if (mBlockPlayer) {
-        setImage(mStaticImagePath);
+    if (!mHasVideo) {
+        // We need this update so the static image gets updated (e.g. used for fade animations).
+        GuiComponent::update(deltaTime);
         return;
     }
 
-    manageState();
+    // Hack to prevent the video from starting to play if the static image was shown when paused.
+    if (mPaused)
+        mStartTime = SDL_GetTicks() + mConfig.startDelay;
+
+    if (mWindow->getGameLaunchedState())
+        return;
+
+    bool playVideo {false};
+
+    if (!mIsPlaying && mConfig.startDelay == 0) {
+        startVideoStream();
+    }
+    else if (mStartTime == 0 || SDL_GetTicks() > mStartTime) {
+        mStartTime = 0;
+        playVideo = true;
+        startVideoStream();
+    }
 
     // Fade in videos, the time period is a bit different between the screensaver and media viewer.
     // For the theme controlled videos in the gamelist and system views, the fade-in time is set
@@ -349,90 +245,37 @@ void VideoComponent::update(int deltaTime)
         mFadeIn = glm::clamp(mFadeIn + (deltaTime / static_cast<float>(mFadeInTime)), 0.0f, 1.0f);
     }
 
+    if (mIsPlaying)
+        updatePlayer();
+
+    handleLooping();
+
     GuiComponent::update(deltaTime);
 }
 
-void VideoComponent::startVideoWithDelay()
+void VideoComponent::startVideoPlayer()
 {
-    mPause = false;
+    if (mIsPlaying)
+        stopVideoPlayer();
 
-    // If not playing then either start the video or initiate the delay.
-    if (!mIsPlaying) {
-        // Set the video that we are going to be playing so we don't attempt to restart it.
-        mPlayingVideoPath = mVideoPath;
-
-        if (mConfig.startDelay == 0) {
-            // No delay. Just start the video.
-            mStartDelayed = false;
-            startVideo();
-        }
-        else {
-            // Configure the start delay.
-            mStartDelayed = true;
-            mStartTime = SDL_GetTicks() + mConfig.startDelay;
-        }
-        mIsPlaying = true;
+    if (mConfig.startDelay != 0 && mStaticImagePath != "") {
+        mStartTime = SDL_GetTicks() + mConfig.startDelay;
+        setImage(mStaticImagePath);
     }
+
+    mPaused = false;
 }
 
-void VideoComponent::handleStartDelay()
+void VideoComponent::renderSnapshot(const glm::mat4& parentTrans)
 {
-    if (mBlockPlayer || mGameLaunched)
+    if (mLegacyTheme && !mHasVideo && !mConfig.showSnapshotNoVideo)
         return;
 
-    // Only play if any delay has timed out.
-    if (mStartDelayed) {
-        // If the setting to override the theme-supplied video delay setting has been enabled,
-        // then play the video immediately.
-        if (!Settings::getInstance()->getBool("PlayVideosImmediately")) {
-            // If there is a video file available but no static image, then start playing the
-            // video immediately regardless of theme configuration or settings.
-            if (mStaticImagePath != "") {
-                if (mStartTime > SDL_GetTicks()) {
-                    // Timeout not yet completed.
-                    return;
-                }
-            }
-        }
-        // Completed.
-        mStartDelayed = false;
-        // Clear the playing flag so startVideo works.
-        mIsPlaying = false;
-        startVideo();
+    if (mHasVideo && (!mConfig.showSnapshotDelay || mConfig.startDelay == 0))
+        return;
+
+    if (mStaticImagePath != "") {
+        mStaticImage.setOpacity(mOpacity * mThemeOpacity);
+        mStaticImage.render(parentTrans);
     }
 }
-
-void VideoComponent::manageState()
-{
-    // We will only show the video if the component is on display and the screensaver
-    // is not active.
-    bool show = mShowing && !mScreensaverActive && !mDisable;
-
-    // See if we're already playing.
-    if (mIsPlaying) {
-        // If we are not on display then stop the video from playing.
-        if (!show) {
-            stopVideo();
-        }
-        else {
-            if (mVideoPath != mPlayingVideoPath) {
-                // Path changed. Stop the video. We will start it again below because
-                // mIsPlaying will be modified by stopVideo to be false.
-                stopVideo();
-            }
-        }
-        updatePlayer();
-    }
-    // Need to recheck variable rather than 'else' because it may be modified above.
-    if (!mIsPlaying) {
-        // If we are on display then see if we should start the video.
-        if (show && !mVideoPath.empty())
-            startVideoWithDelay();
-    }
-
-    // If a game has just been launched and a video is actually shown, then request a
-    // pause of the video so it doesn't continue to play in the background while the
-    // game is running.
-    if (mGameLaunched && show && !mPause)
-        mPause = true;
-}
diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h
index 32eb4a4e1..f5fe43fa7 100644
--- a/es-core/src/components/VideoComponent.h
+++ b/es-core/src/components/VideoComponent.h
@@ -52,27 +52,19 @@ public:
 
     bool hasStaticVideo() { return !mConfig.staticVideoPath.empty(); }
     bool hasStaticImage() { return mStaticImage.getTextureSize() != glm::ivec2 {0, 0}; }
-
-    void onShow() override;
-    void onHide() override;
-    void onStopVideo() override;
-    void onPauseVideo() override;
-    void onUnpauseVideo() override;
-    bool isVideoPaused() override { return mPause; }
-    void onScreensaverActivate() override;
-    void onScreensaverDeactivate() override;
-    void onGameLaunchedActivate() override;
-    void onGameLaunchedDeactivate() override;
-    void topWindow(bool isTop) override;
+    bool hasStartDelay()
+    {
+        if (mLegacyTheme)
+            return mConfig.showSnapshotDelay && mConfig.startDelay > 0;
+        else
+            return mConfig.startDelay > 0;
+    }
 
     // These functions update the embedded static image.
     void onOriginChanged() override { mStaticImage.setOrigin(mOrigin); }
     void onPositionChanged() override { mStaticImage.setPosition(mPosition); }
     void onSizeChanged() override { mStaticImage.onSizeChanged(); }
 
-    void render(const glm::mat4& parentTrans) override;
-    void renderSnapshot(const glm::mat4& parentTrans);
-
     void applyTheme(const std::shared_ptr<ThemeData>& theme,
                     const std::string& view,
                     const std::string& element,
@@ -95,27 +87,21 @@ public:
     virtual void setMaxSize(float width, float height) = 0;
     void setMaxSize(const glm::vec2& size) { setMaxSize(size.x, size.y); }
 
-private:
-    // Start the video immediately.
-    virtual void startVideo() {}
-    // Stop the video.
-    virtual void stopVideo() {}
-    // Pause the video when a game has been launched.
-    virtual void pauseVideo() {}
+    // Basic video controls.
+    void startVideoPlayer();
+    virtual void stopVideoPlayer() {}
+    virtual void pauseVideoPlayer() {}
+
     // Handle looping of the video. Must be called periodically.
     virtual void handleLooping() {}
+    // Used to immediately mute audio even if there are still samples to play in the buffer.
+    virtual void muteVideoPlayer() {}
     virtual void updatePlayer() {}
 
-    // Start the video after any configured delay.
-    void startVideoWithDelay();
-    // Handle any delay to the start of playing the video clip. Must be called periodically.
-    void handleStartDelay();
-    // Manage the playing state of the component.
-    void manageState();
-
-    friend MediaViewer;
-
 protected:
+    virtual void startVideoStream() {}
+    void renderSnapshot(const glm::mat4& parentTrans);
+
     ImageComponent mStaticImage;
 
     unsigned mVideoWidth;
@@ -128,23 +114,17 @@ protected:
     std::string mDefaultImagePath;
 
     std::string mVideoPath;
-    std::string mPlayingVideoPath;
     unsigned mStartTime;
-    bool mStartDelayed;
     std::atomic<bool> mIsPlaying;
     std::atomic<bool> mIsActuallyPlaying;
-    std::atomic<bool> mPause;
-    bool mShowing;
-    bool mDisable;
+    std::atomic<bool> mPaused;
     bool mMediaViewerMode;
-    bool mScreensaverActive;
     bool mScreensaverMode;
-    bool mGameLaunched;
-    bool mBlockPlayer;
     bool mTargetIsMax;
     bool mDrawPillarboxes;
     bool mRenderScanlines;
     bool mLegacyTheme;
+    bool mHasVideo;
     float mFadeIn;
     float mFadeInTime;
 
diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp
index 451796dc3..752b291fe 100644
--- a/es-core/src/components/VideoFFmpegComponent.cpp
+++ b/es-core/src/components/VideoFFmpegComponent.cpp
@@ -54,8 +54,6 @@ VideoFFmpegComponent::VideoFFmpegComponent()
 {
 }
 
-VideoFFmpegComponent::~VideoFFmpegComponent() { stopVideo(); }
-
 void VideoFFmpegComponent::setResize(float width, float height)
 {
     // This resize function is used when stretching videos to full screen in the video screensaver.
@@ -126,7 +124,9 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
     if (!isVisible() || mThemeOpacity == 0.0f)
         return;
 
-    VideoComponent::render(parentTrans);
+    if (!mHasVideo && mStaticImagePath == "")
+        return;
+
     glm::mat4 trans {parentTrans * getTransform()};
     GuiComponent::renderChildren(trans);
 
@@ -209,7 +209,8 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
             pictureLock.unlock();
         }
 
-        mTexture->bind();
+        if (mTexture != nullptr)
+            mTexture->bind();
 
 #if defined(USE_OPENGL_21)
         // Render scanlines if this option is enabled. However, if this is the media viewer
@@ -239,7 +240,7 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
 
 void VideoFFmpegComponent::updatePlayer()
 {
-    if (mPause || !mFormatContext)
+    if (mPaused || !mFormatContext)
         return;
 
     // Output any audio that has been added by the processing thread.
@@ -282,7 +283,7 @@ void VideoFFmpegComponent::frameProcessing()
     if (mAudioCodecContext)
         audioFilter = setupAudioFilters();
 
-    while (mIsPlaying && !mPause && videoFilter && (!mAudioCodecContext || audioFilter)) {
+    while (mIsPlaying && !mPaused && videoFilter && (!mAudioCodecContext || audioFilter)) {
         readFrames();
         if (!mIsPlaying)
             break;
@@ -1073,7 +1074,7 @@ bool VideoFFmpegComponent::decoderInitHW()
         AVCodecContext* checkCodecContext = avcodec_alloc_context3(mHardwareCodec);
 
         if (avcodec_parameters_to_context(checkCodecContext, mVideoStream->codecpar)) {
-            LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+            LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): "
                              "Couldn't fill the video codec context parameters for file \""
                           << mVideoPath << "\"";
             avcodec_free_context(&checkCodecContext);
@@ -1086,7 +1087,7 @@ bool VideoFFmpegComponent::decoderInitHW()
             checkCodecContext->hw_device_ctx = av_buffer_ref(mHwContext);
 
             if (avcodec_open2(checkCodecContext, mHardwareCodec, nullptr)) {
-                LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+                LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): "
                                  "Couldn't initialize the video codec context for file \""
                               << mVideoPath << "\"";
             }
@@ -1174,7 +1175,7 @@ bool VideoFFmpegComponent::decoderInitHW()
     mVideoCodecContext = avcodec_alloc_context3(mHardwareCodec);
 
     if (!mVideoCodecContext) {
-        LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+        LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): "
                          "Couldn't allocate video codec context for file \""
                       << mVideoPath << "\"";
         avcodec_free_context(&mVideoCodecContext);
@@ -1182,7 +1183,7 @@ bool VideoFFmpegComponent::decoderInitHW()
     }
 
     if (avcodec_parameters_to_context(mVideoCodecContext, mVideoStream->codecpar)) {
-        LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+        LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): "
                          "Couldn't fill the video codec context parameters for file \""
                       << mVideoPath << "\"";
         avcodec_free_context(&mVideoCodecContext);
@@ -1193,7 +1194,7 @@ bool VideoFFmpegComponent::decoderInitHW()
     mVideoCodecContext->hw_device_ctx = av_buffer_ref(mHwContext);
 
     if (avcodec_open2(mVideoCodecContext, mHardwareCodec, nullptr)) {
-        LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+        LOG(LogError) << "VideoFFmpegComponent::decoderInitHW(): "
                          "Couldn't initialize the video codec context for file \""
                       << mVideoPath << "\"";
         avcodec_free_context(&mVideoCodecContext);
@@ -1203,8 +1204,10 @@ bool VideoFFmpegComponent::decoderInitHW()
     return false;
 }
 
-void VideoFFmpegComponent::startVideo()
+void VideoFFmpegComponent::startVideoStream()
 {
+    mIsPlaying = true;
+
     if (!mFormatContext) {
         mHardwareCodec = nullptr;
         mHwContext = nullptr;
@@ -1240,14 +1243,14 @@ void VideoFFmpegComponent::startVideo()
         // File operations and basic setup.
 
         if (avformat_open_input(&mFormatContext, filePath.c_str(), nullptr, nullptr)) {
-            LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+            LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
                              "Couldn't open video file \""
                           << mVideoPath << "\"";
             return;
         }
 
         if (avformat_find_stream_info(mFormatContext, nullptr)) {
-            LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+            LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
                              "Couldn't read stream information from video file \""
                           << mVideoPath << "\"";
             return;
@@ -1268,7 +1271,7 @@ void VideoFFmpegComponent::startVideo()
             av_find_best_stream(mFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, &mHardwareCodec, 0);
 
         if (mVideoStreamIndex < 0) {
-            LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+            LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
                              "Couldn't retrieve video stream for file \""
                           << mVideoPath << "\"";
             avformat_close_input(&mFormatContext);
@@ -1280,7 +1283,7 @@ void VideoFFmpegComponent::startVideo()
         mVideoWidth = mFormatContext->streams[mVideoStreamIndex]->codecpar->width;
         mVideoHeight = mFormatContext->streams[mVideoStreamIndex]->codecpar->height;
 
-        LOG(LogDebug) << "VideoFFmpegComponent::startVideo(): "
+        LOG(LogDebug) << "VideoFFmpegComponent::startVideoStream(): "
                       << "Playing video \"" << mVideoPath << "\" (codec: "
                       << avcodec_get_name(
                              mFormatContext->streams[mVideoStreamIndex]->codecpar->codec_id)
@@ -1294,15 +1297,16 @@ void VideoFFmpegComponent::startVideo()
         if (mSWDecoder) {
             // The hardware decoder initialization failed, which can happen for a number of reasons.
             if (hwDecoding) {
-                LOG(LogDebug) << "VideoFFmpegComponent::startVideo(): Hardware decoding failed, "
-                                 "falling back to software decoder";
+                LOG(LogDebug)
+                    << "VideoFFmpegComponent::startVideoStream(): Hardware decoding failed, "
+                       "falling back to software decoder";
             }
 
             mVideoCodec =
                 const_cast<AVCodec*>(avcodec_find_decoder(mVideoStream->codecpar->codec_id));
 
             if (!mVideoCodec) {
-                LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+                LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
                                  "Couldn't find a suitable video codec for file \""
                               << mVideoPath << "\"";
                 return;
@@ -1311,7 +1315,7 @@ void VideoFFmpegComponent::startVideo()
             mVideoCodecContext = avcodec_alloc_context3(mVideoCodec);
 
             if (!mVideoCodecContext) {
-                LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+                LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
                                  "Couldn't allocate video codec context for file \""
                               << mVideoPath << "\"";
                 return;
@@ -1321,14 +1325,14 @@ void VideoFFmpegComponent::startVideo()
                 mVideoCodecContext->flags |= AV_CODEC_FLAG_TRUNCATED;
 
             if (avcodec_parameters_to_context(mVideoCodecContext, mVideoStream->codecpar)) {
-                LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+                LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
                                  "Couldn't fill the video codec context parameters for file \""
                               << mVideoPath << "\"";
                 return;
             }
 
             if (avcodec_open2(mVideoCodecContext, mVideoCodec, nullptr)) {
-                LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+                LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
                                  "Couldn't initialize the video codec context for file \""
                               << mVideoPath << "\"";
                 return;
@@ -1341,7 +1345,7 @@ void VideoFFmpegComponent::startVideo()
             av_find_best_stream(mFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
 
         if (mAudioStreamIndex < 0) {
-            LOG(LogDebug) << "VideoFFmpegComponent::startVideo(): "
+            LOG(LogDebug) << "VideoFFmpegComponent::startVideoStream(): "
                              "File does not seem to contain any audio streams";
         }
 
@@ -1366,14 +1370,14 @@ void VideoFFmpegComponent::startVideo()
                 mAudioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
 
             if (avcodec_parameters_to_context(mAudioCodecContext, mAudioStream->codecpar)) {
-                LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+                LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
                                  "Couldn't fill the audio codec context parameters for file \""
                               << mVideoPath << "\"";
                 return;
             }
 
             if (avcodec_open2(mAudioCodecContext, mAudioCodec, nullptr)) {
-                LOG(LogError) << "VideoFFmpegComponent::startVideo(): "
+                LOG(LogError) << "VideoFFmpegComponent::startVideoStream(): "
                                  "Couldn't initialize the audio codec context for file \""
                               << mVideoPath << "\"";
                 return;
@@ -1402,17 +1406,17 @@ void VideoFFmpegComponent::startVideo()
         // Calculate pillarbox/letterbox sizes.
         calculateBlackRectangle();
 
-        mIsPlaying = true;
         mFadeIn = 0.0f;
     }
 }
 
-void VideoFFmpegComponent::stopVideo()
+void VideoFFmpegComponent::stopVideoPlayer()
 {
+    muteVideoPlayer();
+
     mIsPlaying = false;
     mIsActuallyPlaying = false;
-    mStartDelayed = false;
-    mPause = false;
+    mPaused = false;
     mEndOfVideo = false;
     mTexture.reset();
 
@@ -1451,10 +1455,10 @@ void VideoFFmpegComponent::stopVideo()
     }
 }
 
-void VideoFFmpegComponent::pauseVideo()
+void VideoFFmpegComponent::pauseVideoPlayer()
 {
-    if (mPause && mWindow->getVideoPlayerCount() == 0)
-        AudioManager::getInstance().muteStream();
+    muteVideoPlayer();
+    mPaused = true;
 }
 
 void VideoFFmpegComponent::handleLooping()
@@ -1467,8 +1471,16 @@ void VideoFFmpegComponent::handleLooping()
             mWindow->screensaverTriggerNextGame();
         }
         else {
-            stopVideo();
-            startVideo();
+            stopVideoPlayer();
+            startVideoStream();
         }
     }
 }
+
+void VideoFFmpegComponent::muteVideoPlayer()
+{
+    if (AudioManager::sAudioDevice != 0) {
+        AudioManager::getInstance().clearStream();
+        AudioManager::getInstance().muteStream();
+    }
+}
diff --git a/es-core/src/components/VideoFFmpegComponent.h b/es-core/src/components/VideoFFmpegComponent.h
index 06522b22d..ec3ef8459 100644
--- a/es-core/src/components/VideoFFmpegComponent.h
+++ b/es-core/src/components/VideoFFmpegComponent.h
@@ -33,7 +33,7 @@ class VideoFFmpegComponent : public VideoComponent
 {
 public:
     VideoFFmpegComponent();
-    virtual ~VideoFFmpegComponent();
+    virtual ~VideoFFmpegComponent() { stopVideoPlayer(); }
 
     // Resize the video to fit this size. If one axis is zero, scale that axis to maintain
     // aspect ratio. If both are non-zero, potentially break the aspect ratio. If both are
@@ -45,8 +45,17 @@ public:
     // This can be set before or after a video is loaded.
     // Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive.
     void setMaxSize(float width, float height) override;
+    // Basic video controls.
+    void stopVideoPlayer() override;
+    void pauseVideoPlayer() override;
+    // Handle looping of the video. Must be called periodically.
+    void handleLooping() override;
+    // Used to immediately mute audio even if there are samples to play in the buffer.
+    void muteVideoPlayer() override;
 
 private:
+    void startVideoStream() override;
+
     // Calculates the correct mSize from our resizing information (set by setResize/setMaxSize).
     // Used internally whenever the resizing parameters or texture change.
     void resize();
@@ -74,15 +83,6 @@ private:
     static void detectHWDecoder();
     bool decoderInitHW();
 
-    // Start the video immediately.
-    void startVideo() override;
-    // Stop the video.
-    void stopVideo() override;
-    // Pause the video when a game has been launched.
-    void pauseVideo() override;
-    // Handle looping the video. Must be called periodically.
-    void handleLooping() override;
-
     static enum AVHWDeviceType sDeviceType;
     static enum AVPixelFormat sPixelFormat;
     static std::vector<std::string> sSWDecodedVideos;