ES-DE/es-core/src/components/VideoComponent.cpp
2022-09-15 17:34:20 +02:00

349 lines
12 KiB
C++

// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// VideoComponent.cpp
//
// Base class for playing videos.
//
#include "components/VideoComponent.h"
#include "ThemeData.h"
#include "Window.h"
#include "resources/ResourceManager.h"
#include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
#include <SDL2/SDL_timer.h>
#define SCREENSAVER_FADE_IN_TIME 1100
#define MEDIA_VIEWER_FADE_IN_TIME 600
VideoComponent::VideoComponent()
: mVideoWidth {0}
, mVideoHeight {0}
, mTargetSize {0.0f, 0.0f}
, mVideoAreaPos {0.0f, 0.0f}
, mVideoAreaSize {0.0f, 0.0f}
, mStartTime {0}
, mIsPlaying {false}
, mIsActuallyPlaying {false}
, mPaused {false}
, mMediaViewerMode {false}
, mScreensaverMode {false}
, mTargetIsMax {false}
, mPlayAudio {true}
, mDrawPillarboxes {true}
, mRenderScanlines {false}
, mLegacyTheme {false}
, mHasVideo {false}
, mFadeIn {1.0f}
, mFadeInTime {1000.0f}
{
// Setup default configuration.
mConfig.showSnapshotDelay = false;
mConfig.showSnapshotNoVideo = false;
mConfig.startDelay = 1500;
}
VideoComponent::~VideoComponent()
{
// Stop any currently running video.
stopVideoPlayer();
}
bool VideoComponent::setVideo(std::string path)
{
// Convert the path into a generic format.
std::string fullPath {Utils::FileSystem::getCanonicalPath(path)};
// Check that it's changed.
if (fullPath == mVideoPath)
return !path.empty();
// Store the path.
mVideoPath = fullPath;
// 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.staticVideoPath.empty())
mHasVideo = true;
else
mHasVideo = false;
// Return false to show that no video will be displayed.
return false;
}
void VideoComponent::setImage(const std::string& path, bool tile)
{
std::string imagePath {path};
if (imagePath == "")
imagePath = mDefaultImagePath;
// Check if the image has changed.
if (imagePath == mStaticImagePath)
return;
mStaticImage.setImage(imagePath, tile);
mStaticImagePath = imagePath;
}
void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties)
{
using namespace ThemeFlags;
GuiComponent::applyTheme(theme, view, element,
(properties ^ ThemeFlags::SIZE) |
((properties & (ThemeFlags::SIZE | POSITION)) ? ORIGIN : 0));
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "video")};
mLegacyTheme = theme->isLegacyTheme();
if (!elem)
return;
glm::vec2 scale {getParent() ?
getParent()->getSize() :
glm::vec2 {Renderer::getScreenWidth(), Renderer::getScreenHeight()}};
if (properties & ThemeFlags::SIZE) {
if (elem->has("size")) {
glm::vec2 videoSize {elem->get<glm::vec2>("size")};
if (videoSize == glm::vec2 {0.0f, 0.0f}) {
LOG(LogWarning) << "VideoComponent: Invalid theme configuration, property \"size\" "
"for element \""
<< element.substr(6) << "\" is set to zero";
videoSize = {0.01f, 0.01f};
}
if (videoSize.x > 0.0f)
videoSize.x = glm::clamp(videoSize.x, 0.01f, 2.0f);
if (videoSize.y > 0.0f)
videoSize.y = glm::clamp(videoSize.y, 0.01f, 2.0f);
setResize(videoSize.x * scale.x, videoSize.y * scale.y);
mVideoAreaSize = videoSize * scale;
}
else if (elem->has("maxSize")) {
glm::vec2 videoMaxSize {elem->get<glm::vec2>("maxSize")};
videoMaxSize.x = glm::clamp(videoMaxSize.x, 0.01f, 2.0f);
videoMaxSize.y = glm::clamp(videoMaxSize.y, 0.01f, 2.0f);
setMaxSize(videoMaxSize * scale);
mVideoAreaSize = videoMaxSize * scale;
}
}
if (properties & ThemeFlags::POSITION) {
if (elem->has("pos"))
mVideoAreaPos = elem->get<glm::vec2>("pos") * scale;
}
if (elem->has("metadataElement") && elem->get<bool>("metadataElement"))
mComponentThemeFlags |= ComponentThemeFlags::METADATA_ELEMENT;
if (elem->has("audio"))
mPlayAudio = elem->get<bool>("audio");
if (elem->has("interpolation")) {
const std::string interpolation {elem->get<std::string>("interpolation")};
if (interpolation == "linear") {
mStaticImage.setLinearInterpolation(true);
}
else if (interpolation == "nearest") {
mStaticImage.setLinearInterpolation(false);
}
else {
mStaticImage.setLinearInterpolation(false);
LOG(LogWarning) << "VideoComponent: Invalid theme configuration, property "
"\"interpolation\" for element \""
<< element.substr(6) << "\" defined as \"" << interpolation << "\"";
}
}
if (elem->has("default")) {
const std::string defaultVideo {elem->get<std::string>("default")};
if (ResourceManager::getInstance().fileExists(defaultVideo)) {
mConfig.defaultVideoPath = defaultVideo;
}
else {
LOG(LogWarning)
<< "VideoComponent: File defined for property \"default\" for element \""
<< element.substr(6) << "\" does not exist: \"" << defaultVideo << "\"";
}
}
if (elem->has("defaultImage")) {
mStaticImage.setDefaultImage(elem->get<std::string>("defaultImage"));
mStaticImage.setImage(mStaticImagePath);
mDefaultImagePath = elem->get<std::string>("defaultImage");
}
if (elem->has("path")) {
const std::string staticPath {elem->get<std::string>("path")};
if (ResourceManager::getInstance().fileExists(staticPath)) {
mConfig.staticVideoPath = staticPath;
}
else {
LOG(LogWarning) << "VideoComponent: File defined for property \"path\" for element \""
<< element.substr(6) << "\" does not exist: \"" << staticPath << "\"";
}
}
if ((properties & ThemeFlags::DELAY) && elem->has("delay"))
mConfig.startDelay =
static_cast<unsigned int>(glm::clamp(elem->get<float>("delay"), 0.0f, 15.0f) * 1000.0f);
if (!theme->isLegacyTheme())
mConfig.showSnapshotNoVideo = true;
else if (elem->has("showSnapshotNoVideo"))
mConfig.showSnapshotNoVideo = elem->get<bool>("showSnapshotNoVideo");
if (!theme->isLegacyTheme() && mConfig.startDelay != 0)
mConfig.showSnapshotDelay = true;
else if (elem->has("showSnapshotDelay"))
mConfig.showSnapshotDelay = elem->get<bool>("showSnapshotDelay");
if (properties && elem->has("fadeInTime"))
mFadeInTime = glm::clamp(elem->get<float>("fadeInTime"), 0.0f, 8.0f) * 1000.0f;
if (properties && elem->has("imageType")) {
std::string imageTypes {elem->get<std::string>("imageType")};
for (auto& character : imageTypes) {
if (std::isspace(character))
character = ',';
}
imageTypes = Utils::String::replace(imageTypes, ",,", ",");
mThemeImageTypes = Utils::String::delimitedStringToVector(imageTypes, ",");
if (mThemeImageTypes.empty()) {
LOG(LogError) << "VideoComponent: Invalid theme configuration, property \"imageType\" "
"for element \""
<< element.substr(6) << "\" contains no values";
}
for (std::string& type : mThemeImageTypes) {
if (std::find(supportedImageTypes.cbegin(), supportedImageTypes.cend(), type) ==
supportedImageTypes.cend()) {
LOG(LogError)
<< "VideoComponent: Invalid theme configuration, property \"imageType\" "
"for element \""
<< element.substr(6) << "\" defined as \"" << type << "\"";
mThemeImageTypes.clear();
break;
}
}
std::vector<std::string> sortedTypes {mThemeImageTypes};
std::stable_sort(sortedTypes.begin(), sortedTypes.end());
if (std::adjacent_find(sortedTypes.begin(), sortedTypes.end()) != sortedTypes.end()) {
LOG(LogError) << "VideoComponent: Invalid theme configuration, property \"imageType\" "
"for element \""
<< element.substr(6) << "\" contains duplicate values";
mThemeImageTypes.clear();
}
}
if (elem->has("pillarboxes"))
mDrawPillarboxes = elem->get<bool>("pillarboxes");
if (elem->has("scanlines"))
mRenderScanlines = elem->get<bool>("scanlines");
if (elem->has("scrollFadeIn") && elem->get<bool>("scrollFadeIn"))
mComponentThemeFlags |= ComponentThemeFlags::SCROLL_FADE_IN;
}
std::vector<HelpPrompt> VideoComponent::getHelpPrompts()
{
std::vector<HelpPrompt> ret;
ret.push_back(HelpPrompt("a", "select"));
return ret;
}
void VideoComponent::update(int deltaTime)
{
if (!mHasVideo) {
// We need this update so the static image gets updated (e.g. used for fade animations).
GuiComponent::update(deltaTime);
return;
}
if (mVideoPath == "")
return;
// Hack to prevent the video from starting to play if the static image was shown when paused.
if (mConfig.showSnapshotDelay && mPaused)
mStartTime = SDL_GetTicks() + mConfig.startDelay;
if (mWindow->getGameLaunchedState())
return;
if (!mIsPlaying && (mConfig.startDelay == 0 || mStaticImagePath == "")) {
startVideoStream();
}
else if (mStartTime == 0 || SDL_GetTicks() > mStartTime) {
mStartTime = 0;
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
// via the theme configuration.
if (mScreensaverMode && mFadeIn < 1.0f) {
mFadeIn = glm::clamp(mFadeIn + (deltaTime / static_cast<float>(SCREENSAVER_FADE_IN_TIME)),
0.0f, 1.0f);
}
else if (mMediaViewerMode && mFadeIn < 1.0f) {
mFadeIn = glm::clamp(mFadeIn + (deltaTime / static_cast<float>(MEDIA_VIEWER_FADE_IN_TIME)),
0.0f, 1.0f);
}
else if (mFadeIn < 1.0f) {
mFadeIn = glm::clamp(mFadeIn + (deltaTime / static_cast<float>(mFadeInTime)), 0.0f, 1.0f);
}
if (mIsPlaying)
updatePlayer();
handleLooping();
GuiComponent::update(deltaTime);
}
void VideoComponent::startVideoPlayer()
{
if (mIsPlaying)
stopVideoPlayer();
if (mConfig.showSnapshotDelay && mConfig.startDelay != 0 && mStaticImagePath != "") {
mStartTime = SDL_GetTicks() + mConfig.startDelay;
setImage(mStaticImagePath);
}
mPaused = false;
}
void VideoComponent::renderSnapshot(const glm::mat4& parentTrans)
{
if (mLegacyTheme && !mHasVideo && !mConfig.showSnapshotNoVideo)
return;
if (mHasVideo && (!mConfig.showSnapshotDelay || mConfig.startDelay == 0))
return;
if (mStaticImagePath != "") {
mStaticImage.setOpacity(mOpacity * mThemeOpacity);
mStaticImage.setSaturation(mSaturation * mThemeSaturation);
mStaticImage.setDimming(mDimming);
mStaticImage.render(parentTrans);
}
}