Added a menu option to enable and disable video pillarboxes.

Also did some general code cleanup of VideoVlcComponent.
This commit is contained in:
Leon Styhre 2020-11-17 17:20:25 +01:00
parent bbb5575334
commit 635e7d2cdd
6 changed files with 168 additions and 139 deletions

View file

@ -49,7 +49,7 @@ Many bugs have been fixed, and numerous features that were only partially implem
* Improved input device configuration, and default keyboard mappings are now applied if the keyboard has not been configured by the user
* GUI-configurable option to sort favorite games above non-favorite games (favorites marked with stars)
* GUI-configurable option to sort folders on top of the gamelists
* Added the ability to display a black area around videos with non-standard aspect ratios, for example vertical shooters
* Added the ability to display pillarboxing and letterboxing for videos with non-standard aspect ratios
* Added a gamelist info text field displaying the game count, any applied filters as well as an icon if a folder has been entered
* Expanded the gamelist filter functionality to include completed and broken games as well as the ability to filter on game names (via free text entry)
* Expanded the metadata for folders and made it possible to mark them as favorites

View file

@ -625,6 +625,10 @@ The order in which to sort your gamelists. This can be overriden per game system
Animation to play when opening the main menu or the game options menu. Can be set to _scale-up_, _fade-in_ or _none_.
**Display pillarboxes for gamelist videos**
With this option enabled, there are black pillarboxes (and to a lesser extent letterboxes) displayed around videos with non-standard aspect ratios. This will probably be most commonly used for vertical arcade shooters, or for game systems that has a screen in portrait orientation. For wider than normal videos, letterboxes are added, but this is quite rare compared to videos in portrait orientation. This option looks good on some themes such as rbsimple-DE, but on others it may be more visually pleasing to disable it.
**Render scanlines for gamelist videos** _(OpenGL renderer only)_
Whether to use a shader to render scanlines for videos in the gamelist view. The effect is usually pretty subtle as the video is normally renderered in a limited size in the GUI, and the scanlines are sized relative to the video window size.
@ -739,7 +743,7 @@ Whether to use a shader to render scanlines for the videos.
**Render blur** _(OpenGL renderer only)_
Whether to use a shader to render a slight blur which somewhat simulates a well-used CRT monitor.
Whether to use a shader to render a slight horizontal blur which somewhat simulates a well-used CRT monitor.
### Sound settings

View file

@ -257,7 +257,22 @@ void GuiMenu::openUISettings()
s->setNeedsSaving();
}
});
#endif
// Display pillarboxes (and letterboxes) for videos in the gamelists.
auto gamelist_video_pillarbox = std::make_shared<SwitchComponent>(mWindow);
gamelist_video_pillarbox->setState(Settings::getInstance()->getBool("GamelistVideoPillarbox"));
s->addWithLabel("DISPLAY PILLARBOXES FOR GAMELIST VIDEOS", gamelist_video_pillarbox);
s->addSaveFunc([gamelist_video_pillarbox, s] {
if (gamelist_video_pillarbox->getState() !=
Settings::getInstance()->getBool("GamelistVideoPillarbox")) {
Settings::getInstance()->setBool("GamelistVideoPillarbox",
gamelist_video_pillarbox->getState());
s->setNeedsSaving();
}
});
#if defined(USE_OPENGL_21)
// Render scanlines for videos in the gamelists.
auto gamelist_video_scanlines = std::make_shared<SwitchComponent>(mWindow);
gamelist_video_scanlines->setState(Settings::getInstance()->getBool("GamelistVideoScanlines"));

View file

@ -110,6 +110,7 @@ void Settings::setDefaults()
mStringMap["UIMode"] = "full";
mStringMap["DefaultSortOrder"] = "filename, ascending";
mStringMap["MenuOpeningEffect"] = "scale-up";
mBoolMap["GamelistVideoPillarbox"] = true;
mBoolMap["GamelistVideoScanlines"] = true;
mBoolMap["FoldersOnTop"] = true;
mBoolMap["FavoritesFirst"] = true;

View file

@ -32,7 +32,7 @@ libvlc_instance_t* VideoVlcComponent::mVLC = nullptr;
// VLC prepares to render a video frame.
static void* lock(void* data, void** p_pixels) {
struct VideoContext* c = (struct VideoContext*)data;
struct VideoContext* c = reinterpret_cast<struct VideoContext*>(data);
SDL_LockMutex(c->mutex);
SDL_LockSurface(c->surface);
*p_pixels = c->surface->pixels;
@ -41,7 +41,7 @@ static void* lock(void* data, void** p_pixels) {
// VLC just rendered a video frame.
static void unlock(void* data, void* /*id*/, void *const* /*p_pixels*/) {
struct VideoContext* c = (struct VideoContext*)data;
struct VideoContext* c = reinterpret_cast<struct VideoContext*>(data);
SDL_UnlockSurface(c->surface);
SDL_UnlockMutex(c->mutex);
}
@ -83,12 +83,53 @@ void VideoVlcComponent::setMaxSize(float width, float height)
resize();
}
void VideoVlcComponent::setupVLC()
{
// If VLC hasn't been initialised yet then do it now.
if (!mVLC) {
const char* args[] = { "--quiet" };
#if defined(__APPLE__)
// It's required to set the VLC_PLUGIN_PATH variable on macOS, or the libVLC
// initialization will fail (with no error message).
std::string vlcPluginPath = Utils::FileSystem::getExePath() + "/plugins";
if (Utils::FileSystem::isDirectory(vlcPluginPath))
setenv("VLC_PLUGIN_PATH", vlcPluginPath.c_str(), 1);
else
setenv("VLC_PLUGIN_PATH", "/Applications/VLC.app/Contents/MacOS/plugins/", 1);
#endif
mVLC = libvlc_new(1, args);
}
}
void VideoVlcComponent::setupContext()
{
if (!mContext.valid) {
// Create an RGBA surface to render the video into.
mContext.surface = SDL_CreateRGBSurface(SDL_SWSURFACE, static_cast<int>(mVideoWidth),
static_cast<int>(mVideoHeight), 32, 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff);
mContext.mutex = SDL_CreateMutex();
mContext.valid = true;
resize();
}
}
void VideoVlcComponent::freeContext()
{
if (mContext.valid) {
SDL_FreeSurface(mContext.surface);
SDL_DestroyMutex(mContext.mutex);
mContext.valid = false;
}
}
void VideoVlcComponent::resize()
{
if (!mTexture)
return;
const Vector2f textureSize((float)mVideoWidth, (float)mVideoHeight);
const Vector2f textureSize(static_cast<float>(mVideoWidth), static_cast<float>(mVideoHeight));
if (textureSize == Vector2f::Zero())
return;
@ -97,9 +138,7 @@ void VideoVlcComponent::resize()
// If rounding is off enough in the rasterization step (for images with extreme aspect
// ratios), it can cause cutoff when the aspect ratio breaks.
// So we always make sure the resultant height is an integer to make sure cutoff doesn't
// happen, and scale width from that (you'll see this scattered throughout the function).
// This is probably not the best way, so if you're familiar with this problem and have a
// better solution, please make a pull request!
// happen, and scale width from that.
if (mTargetIsMax) {
mSize = textureSize;
@ -114,7 +153,7 @@ void VideoVlcComponent::resize()
mSize[1] *= resizeScale.y();
}
// For SVG rasterization, always calculate width from rounded height (see comment above).
// For SVG rasterization, always calculate width from rounded height.
mSize[1] = Math::round(mSize[1]);
mSize[0] = (mSize[1] / textureSize.y()) * textureSize.x();
@ -125,7 +164,6 @@ void VideoVlcComponent::resize()
mSize = mTargetSize == Vector2f::Zero() ? textureSize : mTargetSize;
// If only one component is set, we resize in a way that maintains aspect ratio.
// For SVG rasterization, we always calculate width from rounded height (see comment above).
if (!mTargetSize.x() && mTargetSize.y()) {
mSize[1] = Math::round(mTargetSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
@ -136,8 +174,8 @@ void VideoVlcComponent::resize()
}
}
// mSize.y() should already be rounded.
mTexture->rasterizeAt((size_t)Math::round(mSize.x()), (size_t)Math::round(mSize.y()));
mTexture->rasterizeAt(static_cast<size_t>(Math::round(mSize.x())),
static_cast<size_t>(Math::round(mSize.y())));
onSizeChanged();
}
@ -160,9 +198,6 @@ void VideoVlcComponent::render(const Transform4x4f& parentTrans)
mIsActuallyPlaying = true;
}
if (!mIsActuallyPlaying && !mStaticImage.isVisible())
mStaticImage.setVisible(true);
if (mIsPlaying && mContext.valid && mIsActuallyPlaying) {
unsigned int color;
if (mFadeIn < 1) {
@ -179,10 +214,7 @@ void VideoVlcComponent::render(const Transform4x4f& parentTrans)
// Render the black rectangle behind the video.
if (mVideoRectangleCoords.size() == 4) {
Renderer::drawRect(mVideoRectangleCoords[0], mVideoRectangleCoords[1],
mVideoRectangleCoords[2], mVideoRectangleCoords[3],
0x000000FF, 0x000000FF);
if (mStaticImage.isVisible())
mStaticImage.setVisible(false);
mVideoRectangleCoords[2], mVideoRectangleCoords[3], 0x000000FF, 0x000000FF);
}
vertices[0] = { { 0.0f , 0.0f }, { 0.0f, 0.0f }, color };
@ -216,82 +248,60 @@ void VideoVlcComponent::render(const Transform4x4f& parentTrans)
}
}
void VideoVlcComponent::setupContext()
void VideoVlcComponent::calculateBlackRectangle()
{
if (!mContext.valid) {
// Create an RGBA surface to render the video into.
mContext.surface = SDL_CreateRGBSurface(SDL_SWSURFACE, (int)mVideoWidth,
(int)mVideoHeight, 32, 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff);
mContext.mutex = SDL_CreateMutex();
mContext.valid = true;
resize();
}
}
// Calculate the position and size for the black rectangle that will be rendered behind
// videos. If the option to display pillarboxes (and letterboxes) is enabled, then this
// would extend to the entire md_video area (if above the threshold as defined below) or
// otherwise it will exactly match the video size. The reason to add a black rectangle
// behind videos in this second instance is that the scanline rendering will make the
// video partially transparent so this may avoid some unforseen issues with some themes.
if (mVideoAreaPos != 0 && mVideoAreaSize != 0) {
mVideoRectangleCoords.clear();
void VideoVlcComponent::freeContext()
{
if (mContext.valid) {
SDL_FreeSurface(mContext.surface);
SDL_DestroyMutex(mContext.mutex);
mContext.valid = false;
}
}
void VideoVlcComponent::setupVLC()
{
// If VLC hasn't been initialised yet then do it now.
if (!mVLC) {
const char* args[] = { "--quiet" };
#if defined(__APPLE__)
// It's required to set the VLC_PLUGIN_PATH variable on macOS, or the libVLC
// initialization will fail (with no error message).
std::string vlcPluginPath = Utils::FileSystem::getExePath() + "/plugins";
if (Utils::FileSystem::isDirectory(vlcPluginPath))
setenv("VLC_PLUGIN_PATH", vlcPluginPath.c_str(), 1);
if (Settings::getInstance()->getBool("GamelistVideoPillarbox")) {
float rectHeight;
float rectWidth;
// Video is in landscape orientation.
if (mSize.x() > mSize.y()) {
// Checking the Y size should not normally be required as landscape format
// should mean the height can't be higher than the max size defined by the
// theme. But as the height in mSize is provided by libVLC in integer format
// and then scaled, there could be rounding errors that make the video height
// slightly higher than allowed. It's only a single pixel or a few pixels, but
// it's still visible for some videos.
if (mSize.y() < mVideoAreaSize.y() && mSize.y() / mVideoAreaSize.y() < 0.90)
rectHeight = mVideoAreaSize.y();
else
setenv("VLC_PLUGIN_PATH", "/Applications/VLC.app/Contents/MacOS/plugins/", 1);
#endif
mVLC = libvlc_new(1, args);
}
}
void VideoVlcComponent::handleLooping()
{
if (mIsPlaying && mMediaPlayer) {
libvlc_state_t state = libvlc_media_player_get_state(mMediaPlayer);
if (state == libvlc_Ended) {
// If the screensaver video swap time is set to 0, it means we should
// skip to the next game when the video has finished playing.
if (mScreensaverMode &&
Settings::getInstance()->getInt("ScreensaverSwapVideoTimeout") == 0) {
mWindow->screensaverTriggerNextGame();
rectHeight = mSize.y();
// Don't add a black border that is too narrow, that's what the 0.85 constant
// takes care of.
if (mSize.x() < mVideoAreaSize.x() && mSize.x() / mVideoAreaSize.x() < 0.85)
rectWidth = mVideoAreaSize.x();
else
rectWidth = mSize.x();
}
// Video is in portrait orientation (or completely square).
else {
libvlc_media_player_set_media(mMediaPlayer, mMedia);
if ((!Settings::getInstance()->getBool("GamelistVideoAudio") &&
!mScreensaverMode) || (!Settings::getInstance()->
getBool("ScreensaverVideoAudio") && mScreensaverMode))
libvlc_audio_set_mute(mMediaPlayer, 1);
libvlc_media_player_play(mMediaPlayer);
rectWidth = mVideoAreaSize.x();
rectHeight = mSize.y();
}
// Populate the rectangle coordinates to be used in render().
mVideoRectangleCoords.push_back(Math::round(mVideoAreaPos.x() -
rectWidth * mOrigin.x()));
mVideoRectangleCoords.push_back(Math::round(mVideoAreaPos.y() -
rectHeight * mOrigin.y()));
mVideoRectangleCoords.push_back(Math::round(rectWidth));
mVideoRectangleCoords.push_back(Math::round(rectHeight));
}
// If the option to display pillarboxes is disabled, then make the rectangle equivalent
// to the size of the video.
else {
mVideoRectangleCoords.push_back(Math::round(mPosition.x() - mSize.x() * mOrigin.x()));
mVideoRectangleCoords.push_back(Math::round(mPosition.y() - mSize.y() * mOrigin.y()));
mVideoRectangleCoords.push_back(Math::round(mSize.x()));
mVideoRectangleCoords.push_back(Math::round(mSize.y()));
}
}
void VideoVlcComponent::pauseVideo()
{
// If a game has been launched and the flag to pause the video has been
// set, then rewind and pause.
if (!mPause || !mMediaPlayer)
return;
if (libvlc_media_player_get_state(mMediaPlayer) == libvlc_Playing) {
libvlc_media_player_set_position(mMediaPlayer, 0.0f);
libvlc_media_player_pause(mMediaPlayer);
}
}
@ -359,55 +369,16 @@ void VideoVlcComponent::startVideo()
libvlc_media_player_play(mMediaPlayer);
libvlc_video_set_callbacks(mMediaPlayer, lock, unlock, display,
(void*)&mContext);
libvlc_video_set_format(mMediaPlayer, "RGBA", (int)mVideoWidth,
(int)mVideoHeight, (int)mVideoWidth * 4);
reinterpret_cast<void*>(&mContext));
libvlc_video_set_format(mMediaPlayer, "RGBA", static_cast<int>(mVideoWidth),
static_cast<int>(mVideoHeight), static_cast<int>(mVideoWidth * 4));
// Update the playing state.
mIsPlaying = true;
mFadeIn = 0.0f;
}
if (mIsPlaying) {
// Create the position and size for the black rectangle that will be
// rendered behind videos that are not filling the whole md_video area.
if (mVideoAreaPos != 0 && mVideoAreaSize != 0) {
mVideoRectangleCoords.clear();
float rectHeight;
float rectWidth;
// Video is in landscape orientation.
if (mSize.x() > mSize.y()) {
// Checking the Y size should not normally be required as landscape
// format should mean the height can't be higher than the max size
// defined by the theme. But as the height in mSize is provided by
// libVLC in integer format and then scaled, there could be rounding
// errors that make the video height slightly higher than allowed.
// It's only a pixel or so, but it's still visible for some videos.
if (mSize.y() < mVideoAreaSize.y() &&
mSize.y() / mVideoAreaSize.y() < 0.97)
rectHeight = mVideoAreaSize.y();
else
rectHeight = mSize.y();
// Don't add a black border that is too narrow, that's what the
// 0.85 constant takes care of.
if (mSize.x() < mVideoAreaSize.x() &&
mSize.x() / mVideoAreaSize.x() < 0.85)
rectWidth = mVideoAreaSize.x();
else
rectWidth = mSize.x();
}
// Video is in portrait orientation (or completely square).
else {
rectWidth = mVideoAreaSize.x();
rectHeight = mSize.y();
}
// Populate the rectangle coordinates to be used in render().
mVideoRectangleCoords.push_back(mVideoAreaPos.x() -
rectWidth * mOrigin.x());
mVideoRectangleCoords.push_back(mVideoAreaPos.y() -
rectHeight * mOrigin.y());
mVideoRectangleCoords.push_back(rectWidth);
mVideoRectangleCoords.push_back(rectHeight);
}
calculateBlackRectangle();
}
}
}
@ -430,3 +401,41 @@ void VideoVlcComponent::stopVideo()
PowerSaver::resume();
}
}
void VideoVlcComponent::pauseVideo()
{
// If a game has been launched and the flag to pause the video has been
// set, then rewind and pause.
if (!mPause || !mMediaPlayer)
return;
if (libvlc_media_player_get_state(mMediaPlayer) == libvlc_Playing) {
libvlc_media_player_set_position(mMediaPlayer, 0.0f);
libvlc_media_player_pause(mMediaPlayer);
}
}
void VideoVlcComponent::handleLooping()
{
if (mIsPlaying && mMediaPlayer) {
libvlc_state_t state = libvlc_media_player_get_state(mMediaPlayer);
if (state == libvlc_Ended) {
// If the screensaver video swap time is set to 0, it means we should
// skip to the next game when the video has finished playing.
if (mScreensaverMode &&
Settings::getInstance()->getInt("ScreensaverSwapVideoTimeout") == 0) {
mWindow->screensaverTriggerNextGame();
}
else {
libvlc_media_player_set_media(mMediaPlayer, mMedia);
if ((!Settings::getInstance()->getBool("GamelistVideoAudio") &&
!mScreensaverMode) || (!Settings::getInstance()->
getBool("ScreensaverVideoAudio") && mScreensaverMode))
libvlc_audio_set_mute(mMediaPlayer, 1);
libvlc_media_player_play(mMediaPlayer);
}
}
}
}

View file

@ -36,13 +36,9 @@ class VideoVlcComponent : public VideoComponent
};
public:
static void setupVLC();
VideoVlcComponent(Window* window);
virtual ~VideoVlcComponent();
void render(const Transform4x4f& parentTrans) override;
// 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
// zero, no resizing. This can be set before or after a video is loaded.
@ -55,10 +51,17 @@ public:
void setMaxSize(float width, float height) override;
private:
static void setupVLC();
void setupContext();
void freeContext();
// Calculates the correct mSize from our resizing information (set by setResize/setMaxSize).
// Used internally whenever the resizing parameters or texture change.
void resize();
void render(const Transform4x4f& parentTrans) override;
void calculateBlackRectangle();
// Start the video immediately.
virtual void startVideo() override;
// Stop the video.
@ -68,9 +71,6 @@ private:
// Handle looping the video. Must be called periodically.
virtual void handleLooping() override;
void setupContext();
void freeContext();
static void VlcMediaParseCallback(const libvlc_event_t *event, void *user_data) {};
private: