Greatly simplified the video controls code.

Also fixed a cosmetic issue with carousel fade transitions.
This commit is contained in:
Leon Styhre 2022-02-19 17:04:23 +01:00
parent ee1a0f7cd3
commit c4eb1b8b97
22 changed files with 229 additions and 466 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -175,7 +175,6 @@ GuiScraperMulti::~GuiScraperMulti()
(*it)->sortSystem();
}
}
ViewController::getInstance()->onPauseVideo();
}
void GuiScraperMulti::onSizeChanged()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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