Merge pull request #135 from pjft/RetroPie-OMX-Player

Adding OMX Player as experimental option for ES
This commit is contained in:
Jools Wills 2017-05-28 22:27:49 +01:00 committed by GitHub
commit bc68e0abb0
15 changed files with 797 additions and 391 deletions

View file

@ -78,7 +78,7 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN
// disable sounds
auto sounds_enabled = std::make_shared<SwitchComponent>(mWindow);
sounds_enabled->setState(Settings::getInstance()->getBool("EnableSounds"));
s->addWithLabel("ENABLE SOUNDS", sounds_enabled);
s->addWithLabel("ENABLE NAVIGATION SOUNDS", sounds_enabled);
s->addSaveFunc([sounds_enabled] { Settings::getInstance()->setBool("EnableSounds", sounds_enabled->getState()); });
mWindow->pushGui(s);
@ -181,6 +181,36 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN
mWindow->pushGui(s);
});
addEntry("VIDEO PLAYER SETTINGS", 0x777777FF, true,
[this] {
auto s = new GuiSettings(mWindow, "VIDEO PLAYER SETTINGS");
#ifdef _RPI_
// Video Player - VideoOmxPlayer
auto omx_player = std::make_shared<SwitchComponent>(mWindow);
omx_player->setState(Settings::getInstance()->getBool("VideoOmxPlayer"));
s->addWithLabel("USE OMX VIDEO PLAYER (HW ACCELERATED)", omx_player);
s->addSaveFunc([omx_player]
{
// need to reload all views to re-create the right video components
bool needReload = false;
if(Settings::getInstance()->getBool("VideoOmxPlayer") != omx_player->getState())
needReload = true;
Settings::getInstance()->setBool("VideoOmxPlayer", omx_player->getState());
if(needReload)
ViewController::get()->reloadAll();
});
#endif
auto video_audio = std::make_shared<SwitchComponent>(mWindow);
video_audio->setState(Settings::getInstance()->getBool("VideoAudio"));
s->addWithLabel("ENABLE VIDEO AUDIO", video_audio);
s->addSaveFunc([video_audio] { Settings::getInstance()->setBool("VideoAudio", video_audio->getState()); });
mWindow->pushGui(s);
});
addEntry("OTHER SETTINGS", 0x777777FF, true,
[this] {
auto s = new GuiSettings(mWindow, "OTHER SETTINGS");

View file

@ -4,13 +4,18 @@
#include "animations/LambdaAnimation.h"
#include <sys/stat.h>
#include <fcntl.h>
#ifdef _RPI_
#include "components/VideoPlayerComponent.h"
#include "Settings.h"
#endif
#include "components/VideoVlcComponent.h"
VideoGameListView::VideoGameListView(Window* window, FileData* root) :
BasicGameListView(window, root),
mDescContainer(window), mDescription(window),
mMarquee(window),
mImage(window),
mVideo(window),
mVideo(nullptr),
mVideoPlaying(false),
mLblRating(window), mLblReleaseDate(window), mLblDeveloper(window), mLblPublisher(window),
@ -21,6 +26,16 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) :
{
const float padding = 0.01f;
// Create the correct type of video window
#ifdef _RPI_
if (Settings::getInstance()->getBool("VideoOmxPlayer"))
mVideo = new VideoPlayerComponent(window);
else
mVideo = new VideoVlcComponent(window);
#else
mVideo = new VideoVlcComponent(window);
#endif
mList.setPosition(mSize.x() * (0.50f + padding), mList.getPosition().y());
mList.setSize(mSize.x() * (0.50f - padding), mList.getSize().y());
mList.setAlignment(TextListComponent<FileData*>::ALIGN_LEFT);
@ -42,11 +57,11 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) :
addChild(&mImage);
// video
mVideo.setOrigin(0.5f, 0.5f);
mVideo.setPosition(mSize.x() * 0.25f, mSize.y() * 0.4f);
mVideo.setSize(mSize.x() * (0.5f - 2*padding), mSize.y() * 0.4f);
mVideo.setDefaultZIndex(30);
addChild(&mVideo);
mVideo->setOrigin(0.5f, 0.5f);
mVideo->setPosition(mSize.x() * 0.25f, mSize.y() * 0.4f);
mVideo->setSize(mSize.x() * (0.5f - 2*padding), mSize.y() * 0.4f);
mVideo->setDefaultZIndex(30);
addChild(mVideo);
// metadata labels + values
mLblRating.setText("Rating: ");
@ -91,6 +106,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) :
VideoGameListView::~VideoGameListView()
{
delete mVideo;
}
void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
@ -100,7 +116,7 @@ void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
using namespace ThemeFlags;
mMarquee.applyTheme(theme, getName(), "md_marquee", POSITION | ThemeFlags::SIZE | Z_INDEX);
mImage.applyTheme(theme, getName(), "md_image", POSITION | ThemeFlags::SIZE | Z_INDEX);
mVideo.applyTheme(theme, getName(), "md_video", POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX);
mVideo->applyTheme(theme, getName(), "md_video", POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY | Z_INDEX);
initMDLabels();
std::vector<TextComponent*> labels = getMDLabels();
@ -214,8 +230,8 @@ void VideoGameListView::updateInfoPanel()
bool fadingOut;
if(file == NULL)
{
mVideo.setVideo("");
mVideo.setImage("");
mVideo->setVideo("");
mVideo->setImage("");
mVideoPlaying = false;
//mMarquee.setImage("");
//mDescription.setText("");
@ -244,13 +260,13 @@ void VideoGameListView::updateInfoPanel()
thumbnail_path.erase(0, 1);
thumbnail_path.insert(0, getHomePath());
}
if (!mVideo.setVideo(video_path))
if (!mVideo->setVideo(video_path))
{
mVideo.setDefaultVideo();
mVideo->setDefaultVideo();
}
mVideoPlaying = true;
mVideo.setImage(thumbnail_path);
mVideo->setImage(thumbnail_path);
mMarquee.setImage(marquee_path);
mImage.setImage(thumbnail_path);
@ -275,7 +291,7 @@ void VideoGameListView::updateInfoPanel()
std::vector<GuiComponent*> comps = getMDValues();
comps.push_back(&mMarquee);
comps.push_back(&mVideo);
comps.push_back(mVideo);
comps.push_back(&mDescription);
comps.push_back(&mImage);
std::vector<TextComponent*> labels = getMDLabels();
@ -304,7 +320,7 @@ void VideoGameListView::launch(FileData* game)
{
Eigen::Vector3f target(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f, 0);
if(mMarquee.hasImage())
target << mVideo.getCenter().x(), mVideo.getCenter().y(), 0;
target << mVideo->getCenter().x(), mVideo->getCenter().y(), 0;
ViewController::get()->launch(game, target);
}
@ -340,7 +356,7 @@ std::vector<GuiComponent*> VideoGameListView::getMDValues()
void VideoGameListView::update(int deltaTime)
{
BasicGameListView::update(deltaTime);
mVideo.update(deltaTime);
mVideo->update(deltaTime);
}
void VideoGameListView::onShow()

View file

@ -30,7 +30,7 @@ private:
void initMDValues();
ImageComponent mMarquee;
VideoComponent mVideo;
VideoComponent* mVideo;
ImageComponent mImage;
TextComponent mLblRating, mLblReleaseDate, mLblDeveloper, mLblPublisher, mLblGenre, mLblPlayers, mLblLastPlayed, mLblPlayCount;

View file

@ -43,6 +43,8 @@ set(CORE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoPlayerComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.h
# Guis
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.h
@ -99,6 +101,8 @@ set(CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoPlayerComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoVlcComponent.cpp
# Guis
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.cpp

View file

@ -390,4 +390,20 @@ void GuiComponent::onHide()
getChild(i)->onHide();
}
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::topWindow(bool isTop)
{
for(unsigned int i = 0; i < getChildCount(); i++)
getChild(i)->topWindow(isTop);
}

View file

@ -87,6 +87,9 @@ public:
virtual void onShow();
virtual void onHide();
virtual void onScreenSaverActivate();
virtual void onScreenSaverDeactivate();
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.

View file

@ -76,6 +76,12 @@ void Settings::setDefaults()
mStringMap["ScreenSaverBehavior"] = "dim";
mStringMap["Scraper"] = "TheGamesDB";
mStringMap["GamelistViewStyle"] = "automatic";
// This setting only applies to raspberry pi but set it for all platforms so
// we don't get a warning if we encounter it on a different platform
mBoolMap["VideoOmxPlayer"] = false;
mBoolMap["VideoAudio"] = true;
}
template <typename K, typename V>

View file

@ -29,6 +29,11 @@ Window::~Window()
void Window::pushGui(GuiComponent* gui)
{
if (mGuiStack.size() > 0)
{
auto& top = mGuiStack.back();
top->topWindow(false);
}
mGuiStack.push_back(gui);
gui->updateHelpPrompts();
}
@ -42,7 +47,10 @@ void Window::removeGui(GuiComponent* gui)
i = mGuiStack.erase(i);
if(i == mGuiStack.end() && mGuiStack.size()) // we just popped the stack and the stack is not empty
{
mGuiStack.back()->updateHelpPrompts();
mGuiStack.back()->topWindow(true);
}
return;
}
@ -107,10 +115,20 @@ void Window::textInput(const char* text)
void Window::input(InputConfig* config, Input input)
{
if (mRenderScreenSaver)
{
mRenderScreenSaver = false;
// Tell the GUI components the screensaver has stopped
for(auto i = mGuiStack.begin(); i != mGuiStack.end(); i++)
(*i)->onScreenSaverDeactivate();
}
if(mSleeping)
{
// wake up
mTimeSinceLastInput = 0;
mSleeping = false;
onWake();
return;
@ -210,6 +228,13 @@ void Window::render()
unsigned int screensaverTime = (unsigned int)Settings::getInstance()->getInt("ScreenSaverTime");
if(mTimeSinceLastInput >= screensaverTime && screensaverTime != 0)
{
if (!mRenderScreenSaver)
{
for(auto i = mGuiStack.begin(); i != mGuiStack.end(); i++)
(*i)->onScreenSaverActivate();
mRenderScreenSaver = true;
}
renderScreenSaver();
if (!isProcessing() && mAllowSleep)

View file

@ -17,6 +17,7 @@ public:
void pushGui(GuiComponent* gui);
void removeGui(GuiComponent* gui);
GuiComponent* peekGui();
inline int getGuiStackSize() { return mGuiStack.size(); }
void textInput(const char* text);
void input(InputConfig* config, Input input);
@ -55,6 +56,7 @@ private:
int mFrameTimeElapsed;
int mFrameCountElapsed;
int mAverageDeltaTime;
bool mRenderScreenSaver;
std::unique_ptr<TextCache> mFrameDataText;

View file

@ -2,60 +2,35 @@
#include "Renderer.h"
#include "ThemeData.h"
#include "Util.h"
#include "Window.h"
#ifdef WIN32
#include <codecvt>
#endif
#define FADE_TIME_MS 200
libvlc_instance_t* VideoComponent::mVLC = NULL;
// VLC prepares to render a video frame.
static void *lock(void *data, void **p_pixels) {
struct VideoContext *c = (struct VideoContext *)data;
SDL_LockMutex(c->mutex);
SDL_LockSurface(c->surface);
*p_pixels = c->surface->pixels;
return NULL; // Picture identifier, not needed here.
}
// VLC just rendered a video frame.
static void unlock(void *data, void *id, void *const *p_pixels) {
struct VideoContext *c = (struct VideoContext *)data;
SDL_UnlockSurface(c->surface);
SDL_UnlockMutex(c->mutex);
}
// VLC wants to display a video frame.
static void display(void *data, void *id) {
//Data to be displayed
}
VideoComponent::VideoComponent(Window* window) :
GuiComponent(window),
mStaticImage(window),
mMediaPlayer(nullptr),
mVideoHeight(0),
mVideoWidth(0),
mStartDelayed(false),
mIsPlaying(false),
mShowing(false),
mScreensaverActive(false),
mDisable(false),
mTargetIsMax(false),
mOrigin(0, 0),
mTargetSize(0, 0)
{
memset(&mContext, 0, sizeof(mContext));
// Setup the default configuration
mConfig.showSnapshotDelay = false;
mConfig.showSnapshotNoVideo = false;
mConfig.startDelay = 0;
if (mWindow->getGuiStackSize() > 1) {
topWindow(false);
}
// Get an empty texture for rendering the video
mTexture = TextureResource::get("");
// Make sure VLC has been initialised
setupVLC();
}
VideoComponent::~VideoComponent()
@ -72,89 +47,12 @@ void VideoComponent::setOrigin(float originX, float originY)
mStaticImage.setOrigin(originX, originY);
}
void VideoComponent::setResize(float width, float height)
{
mTargetSize << width, height;
mTargetIsMax = false;
mStaticImage.setResize(width, height);
resize();
}
void VideoComponent::setMaxSize(float width, float height)
{
mTargetSize << width, height;
mTargetIsMax = true;
mStaticImage.setMaxSize(width, height);
resize();
}
Eigen::Vector2f VideoComponent::getCenter() const
{
return Eigen::Vector2f(mPosition.x() - (getSize().x() * mOrigin.x()) + getSize().x() / 2,
mPosition.y() - (getSize().y() * mOrigin.y()) + getSize().y() / 2);
}
void VideoComponent::resize()
{
if(!mTexture)
return;
const Eigen::Vector2f textureSize(mVideoWidth, mVideoHeight);
if(textureSize.isZero())
return;
// SVG rasterization is determined by height (see SVGResource.cpp), and rasterization is done in terms of pixels
// 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!
if(mTargetIsMax)
{
mSize = textureSize;
Eigen::Vector2f resizeScale((mTargetSize.x() / mSize.x()), (mTargetSize.y() / mSize.y()));
if(resizeScale.x() < resizeScale.y())
{
mSize[0] *= resizeScale.x();
mSize[1] *= resizeScale.x();
}else{
mSize[0] *= resizeScale.y();
mSize[1] *= resizeScale.y();
}
// for SVG rasterization, always calculate width from rounded height (see comment above)
mSize[1] = round(mSize[1]);
mSize[0] = (mSize[1] / textureSize.y()) * textureSize.x();
}else{
// if both components are set, we just stretch
// if no components are set, we don't resize at all
mSize = mTargetSize.isZero() ? 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] = round(mTargetSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}else if(mTargetSize.x() && !mTargetSize.y())
{
mSize[1] = round((mTargetSize.x() / textureSize.x()) * textureSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}
}
// mSize.y() should already be rounded
mTexture->rasterizeAt((int)round(mSize.x()), (int)round(mSize.y()));
onSizeChanged();
}
void VideoComponent::onSizeChanged()
{
// Update the embeded static image
@ -163,7 +61,7 @@ void VideoComponent::onSizeChanged()
bool VideoComponent::setVideo(std::string path)
{
// Convert the path into a format VLC can understand
// Convert the path into a generic format
boost::filesystem::path fullPath = getCanonicalPath(path);
fullPath.make_preferred().native();
@ -191,6 +89,8 @@ void VideoComponent::setImage(std::string path)
return;
mStaticImage.setImage(path);
// Make the image stretch to fill the video region
mStaticImage.setSize(getSize());
mFadeIn = 0.0f;
mStaticImagePath = path;
}
@ -222,76 +122,7 @@ void VideoComponent::render(const Eigen::Affine3f& parentTrans)
// Handle looping of the video
handleLooping();
if (mIsPlaying && mContext.valid)
{
float tex_offs_x = 0.0f;
float tex_offs_y = 0.0f;
float x2;
float y2;
x = -(float)mSize.x() * mOrigin.x();
y = -(float)mSize.y() * mOrigin.y();
x2 = x+mSize.x();
y2 = y+mSize.y();
// Define a structure to contain the data for each vertex
struct Vertex
{
Eigen::Vector2f pos;
Eigen::Vector2f tex;
Eigen::Vector4f colour;
} vertices[6];
// We need two triangles to cover the rectangular area
vertices[0].pos[0] = x; vertices[0].pos[1] = y;
vertices[1].pos[0] = x; vertices[1].pos[1] = y2;
vertices[2].pos[0] = x2; vertices[2].pos[1] = y;
vertices[3].pos[0] = x2; vertices[3].pos[1] = y;
vertices[4].pos[0] = x; vertices[4].pos[1] = y2;
vertices[5].pos[0] = x2; vertices[5].pos[1] = y2;
// Texture coordinates
vertices[0].tex[0] = -tex_offs_x; vertices[0].tex[1] = -tex_offs_y;
vertices[1].tex[0] = -tex_offs_x; vertices[1].tex[1] = 1.0f + tex_offs_y;
vertices[2].tex[0] = 1.0f + tex_offs_x; vertices[2].tex[1] = -tex_offs_y;
vertices[3].tex[0] = 1.0f + tex_offs_x; vertices[3].tex[1] = -tex_offs_y;
vertices[4].tex[0] = -tex_offs_x; vertices[4].tex[1] = 1.0f + tex_offs_y;
vertices[5].tex[0] = 1.0f + tex_offs_x; vertices[5].tex[1] = 1.0f + tex_offs_y;
// Colours - use this to fade the video in and out
for (int i = 0; i < (4 * 6); ++i) {
if ((i%4) < 3)
vertices[i / 4].colour[i % 4] = mFadeIn;
else
vertices[i / 4].colour[i % 4] = 1.0f;
}
glEnable(GL_TEXTURE_2D);
// Build a texture for the video frame
mTexture->initFromPixels((unsigned char*)mContext.surface->pixels, mContext.surface->w, mContext.surface->h);
mTexture->bind();
// Render it
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glColorPointer(4, GL_FLOAT, sizeof(Vertex), &vertices[0].colour);
glVertexPointer(2, GL_FLOAT, sizeof(Vertex), &vertices[0].pos);
glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &vertices[0].tex);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisable(GL_TEXTURE_2D);
}
else
if (!mIsPlaying)
{
// This is the case where the video is not currently being displayed. Work out
// if we need to display a static image
@ -361,38 +192,6 @@ std::vector<HelpPrompt> VideoComponent::getHelpPrompts()
return ret;
}
void VideoComponent::setupContext()
{
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();
}
}
void VideoComponent::freeContext()
{
if (mContext.valid)
{
SDL_FreeSurface(mContext.surface);
SDL_DestroyMutex(mContext.mutex);
mContext.valid = false;
}
}
void VideoComponent::setupVLC()
{
// If VLC hasn't been initialised yet then do it now
if (!mVLC)
{
const char* args[] = { "--quiet" };
mVLC = libvlc_new(sizeof(args) / sizeof(args[0]), args);
}
}
void VideoComponent::handleStartDelay()
{
// Only play if any delay has timed out
@ -413,74 +212,6 @@ void VideoComponent::handleStartDelay()
void VideoComponent::handleLooping()
{
if (mIsPlaying && mMediaPlayer)
{
libvlc_state_t state = libvlc_media_player_get_state(mMediaPlayer);
if (state == libvlc_Ended)
{
//libvlc_media_player_set_position(mMediaPlayer, 0.0f);
libvlc_media_player_set_media(mMediaPlayer, mMedia);
libvlc_media_player_play(mMediaPlayer);
}
}
}
void VideoComponent::startVideo()
{
if (!mIsPlaying) {
mVideoWidth = 0;
mVideoHeight = 0;
#ifdef WIN32
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> wton;
std::string path = wton.to_bytes(mVideoPath.c_str());
#else
std::string path(mVideoPath.c_str());
#endif
// Make sure we have a video path
if (mVLC && (path.size() > 0))
{
// Set the video that we are going to be playing so we don't attempt to restart it
mPlayingVideoPath = mVideoPath;
// Open the media
mMedia = libvlc_media_new_path(mVLC, path.c_str());
if (mMedia)
{
unsigned track_count;
// Get the media metadata so we can find the aspect ratio
libvlc_media_parse(mMedia);
libvlc_media_track_t** tracks;
track_count = libvlc_media_tracks_get(mMedia, &tracks);
for (unsigned track = 0; track < track_count; ++track)
{
if (tracks[track]->i_type == libvlc_track_video)
{
mVideoWidth = tracks[track]->video->i_width;
mVideoHeight = tracks[track]->video->i_height;
break;
}
}
libvlc_media_tracks_release(tracks, track_count);
// Make sure we found a valid video track
if ((mVideoWidth > 0) && (mVideoHeight > 0))
{
setupContext();
// Setup the media player
mMediaPlayer = libvlc_media_player_new_from_media(mMedia);
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);
// Update the playing state
mIsPlaying = true;
mFadeIn = 0.0f;
}
}
}
}
}
void VideoComponent::startVideoWithDelay()
@ -508,21 +239,6 @@ void VideoComponent::startVideoWithDelay()
}
}
void VideoComponent::stopVideo()
{
mIsPlaying = false;
mStartDelayed = false;
// Release the media player so it stops calling back to us
if (mMediaPlayer)
{
libvlc_media_player_stop(mMediaPlayer);
libvlc_media_player_release(mMediaPlayer);
libvlc_media_release(mMedia);
mMediaPlayer = NULL;
freeContext();
}
}
void VideoComponent::update(int deltaTime)
{
manageState();
@ -554,8 +270,9 @@ void VideoComponent::update(int deltaTime)
void VideoComponent::manageState()
{
// We will only show if the component is on display
bool show = mShowing;
// We will only show 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)
@ -598,4 +315,20 @@ void VideoComponent::onHide()
manageState();
}
void VideoComponent::onScreenSaverActivate()
{
mScreensaverActive = true;
manageState();
}
void VideoComponent::onScreenSaverDeactivate()
{
mScreensaverActive = false;
manageState();
}
void VideoComponent::topWindow(bool isTop)
{
mDisable = !isTop;
manageState();
}

View file

@ -8,18 +8,10 @@
#include "ImageComponent.h"
#include <string>
#include <memory>
#include "resources/TextureResource.h"
#include <vlc/vlc.h>
#include <SDL.h>
#include <SDL_mutex.h>
#include <boost/filesystem.hpp>
struct VideoContext {
SDL_Surface* surface;
SDL_mutex* mutex;
bool valid;
};
class VideoComponent : public GuiComponent
{
// Structure that groups together the configuration of the video component
@ -32,8 +24,6 @@ class VideoComponent : public GuiComponent
};
public:
static void setupVLC();
VideoComponent(Window* window);
virtual ~VideoComponent();
@ -47,6 +37,9 @@ public:
virtual void onShow() override;
virtual void onHide() override;
virtual void onScreenSaverActivate() override;
virtual void onScreenSaverDeactivate() override;
virtual void topWindow(bool isTop) override;
//Sets the origin as a percentage of this image (e.g. (0, 0) is top left, (0.5, 0.5) is the center)
void setOrigin(float originX, float originY);
@ -55,19 +48,6 @@ public:
void onSizeChanged() override;
void setOpacity(unsigned char opacity) 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.
// Can be set before or after a video is loaded.
// setMaxSize() and setResize() are mutually exclusive.
void setResize(float width, float height);
inline void setResize(const Eigen::Vector2f& size) { setResize(size.x(), size.y()); }
// Resize the video to be as large as possible but fit within a box of this size.
// 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);
inline void setMaxSize(const Eigen::Vector2f& size) { setMaxSize(size.x(), size.y()); }
void render(const Eigen::Affine3f& parentTrans) override;
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
@ -79,35 +59,37 @@ public:
virtual void update(int deltaTime);
private:
// Calculates the correct mSize from our resizing information (set by setResize/setMaxSize).
// Used internally whenever the resizing parameters or texture change.
void resize();
// 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.
// Can be set before or after a video is loaded.
// setMaxSize() and setResize() are mutually exclusive.
virtual void setResize(float width, float height) = 0;
inline void setResize(const Eigen::Vector2f& size) { setResize(size.x(), size.y()); }
// Resize the video to be as large as possible but fit within a box of this size.
// Can be set before or after a video is loaded.
// Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive.
virtual void setMaxSize(float width, float height) = 0;
inline void setMaxSize(const Eigen::Vector2f& size) { setMaxSize(size.x(), size.y()); }
private:
// Start the video Immediately
void startVideo();
virtual void startVideo() = 0;
// Stop the video
virtual void stopVideo() { };
// Handle looping the video. Must be called periodically
virtual void handleLooping();
// Start the video after any configured delay
void startVideoWithDelay();
// Stop the video
void stopVideo();
void setupContext();
void freeContext();
// Handle any delay to the start of playing the video clip. Must be called periodically
void handleStartDelay();
// Handle looping the video. Must be called periodically
void handleLooping();
// Manage the playing state of the component
void manageState();
private:
static libvlc_instance_t* mVLC;
libvlc_media_t* mMedia;
libvlc_media_player_t* mMediaPlayer;
VideoContext mContext;
protected:
unsigned mVideoWidth;
unsigned mVideoHeight;
Eigen::Vector2f mOrigin;
@ -123,6 +105,8 @@ private:
unsigned mStartTime;
bool mIsPlaying;
bool mShowing;
bool mDisable;
bool mScreensaverActive;
bool mTargetIsMax;
Configuration mConfig;

View file

@ -0,0 +1,142 @@
#ifdef _RPI_
#include "components/VideoPlayerComponent.h"
#include "Renderer.h"
#include "ThemeData.h"
#include "Settings.h"
#include "Util.h"
#include <signal.h>
#include <wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
VideoPlayerComponent::VideoPlayerComponent(Window* window) :
VideoComponent(window),
mPlayerPid(-1)
{
}
VideoPlayerComponent::~VideoPlayerComponent()
{
}
void VideoPlayerComponent::render(const Eigen::Affine3f& parentTrans)
{
VideoComponent::render(parentTrans);
}
void VideoPlayerComponent::setResize(float width, float height)
{
setSize(width, height);
mTargetSize << width, height;
mTargetIsMax = false;
mStaticImage.setSize(width, height);
onSizeChanged();
}
void VideoPlayerComponent::setMaxSize(float width, float height)
{
setSize(width, height);
mTargetSize << width, height;
mTargetIsMax = true;
mStaticImage.setMaxSize(width, height);
onSizeChanged();
}
void VideoPlayerComponent::startVideo()
{
if (!mIsPlaying) {
mVideoWidth = 0;
mVideoHeight = 0;
std::string path(mVideoPath.c_str());
// Make sure we have a video path
if ((path.size() > 0) && (mPlayerPid == -1))
{
// Set the video that we are going to be playing so we don't attempt to restart it
mPlayingVideoPath = mVideoPath;
// Start the player process
pid_t pid = fork();
if (pid == -1)
{
// Failed
mPlayingVideoPath = "";
}
else if (pid > 0)
{
mPlayerPid = pid;
// Update the playing state
signal(SIGCHLD, catch_child);
mIsPlaying = true;
mFadeIn = 0.0f;
}
else
{
// Find out the pixel position of the video view and build a command line for
// omxplayer to position it in the right place
char buf[32];
float x = mPosition.x() - (mOrigin.x() * mSize.x());
float y = mPosition.y() - (mOrigin.y() * mSize.y());
sprintf(buf, "%d,%d,%d,%d", (int)x, (int)y, (int)(x + mSize.x()), (int)(y + mSize.y()));
// We need to specify the layer of 10000 or above to ensure the video is displayed on top
// of our SDL display
const char* argv[] = { "", "--layer", "10010", "--loop", "--no-osd", "--aspect-mode", "letterbox", "--vol", "0", "--win", buf, "-b", "", "", "", "", NULL };
// check if we want to mute the audio
if (!Settings::getInstance()->getBool("VideoAudio"))
{
argv[8] = "-1000000";
}
// if we are rendering a video gamelist
if (!mTargetIsMax)
{
argv[6] = "stretch";
}
argv[11] = mPlayingVideoPath.c_str();
const char* env[] = { "LD_LIBRARY_PATH=/opt/vc/libs:/usr/lib/omxplayer", NULL };
// Redirect stdout
int fdin = open("/dev/null", O_RDONLY);
int fdout = open("/dev/null", O_WRONLY);
dup2(fdin, 0);
dup2(fdout, 1);
// Run the omxplayer binary
execve("/usr/bin/omxplayer.bin", (char**)argv, (char**)env);
_exit(EXIT_FAILURE);
}
}
}
}
void catch_child(int sig_num)
{
/* when we get here, we know there's a zombie child waiting */
int child_status;
wait(&child_status);
}
void VideoPlayerComponent::stopVideo()
{
mIsPlaying = false;
mStartDelayed = false;
// Stop the player process
if (mPlayerPid != -1)
{
int status;
kill(mPlayerPid, SIGKILL);
waitpid(mPlayerPid, &status, WNOHANG);
mPlayerPid = -1;
}
}
#endif

View file

@ -0,0 +1,43 @@
#ifdef _RPI_
#ifndef _VIDEOPLAYERCOMPONENT_H_
#define _VIDEOPLAYERCOMPONENT_H_
#include "platform.h"
#include GLHEADER
#include "components/VideoComponent.h"
void catch_child(int sig_num);
class VideoPlayerComponent : public VideoComponent
{
public:
VideoPlayerComponent(Window* window);
virtual ~VideoPlayerComponent();
void render(const Eigen::Affine3f& 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.
// Can be set before or after a video is loaded.
// setMaxSize() and setResize() are mutually exclusive.
void setResize(float width, float height);
// Resize the video to be as large as possible but fit within a box of this size.
// 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);
private:
// Start the video Immediately
virtual void startVideo();
// Stop the video
virtual void stopVideo();
private:
pid_t mPlayerPid;
};
#endif
#endif

View file

@ -0,0 +1,332 @@
#include "components/VideoVlcComponent.h"
#include "Renderer.h"
#include "ThemeData.h"
#include "Util.h"
#include "Settings.h"
#ifdef WIN32
#include <codecvt>
#endif
libvlc_instance_t* VideoVlcComponent::mVLC = NULL;
// VLC prepares to render a video frame.
static void *lock(void *data, void **p_pixels) {
struct VideoContext *c = (struct VideoContext *)data;
SDL_LockMutex(c->mutex);
SDL_LockSurface(c->surface);
*p_pixels = c->surface->pixels;
return NULL; // Picture identifier, not needed here.
}
// VLC just rendered a video frame.
static void unlock(void *data, void *id, void *const *p_pixels) {
struct VideoContext *c = (struct VideoContext *)data;
SDL_UnlockSurface(c->surface);
SDL_UnlockMutex(c->mutex);
}
// VLC wants to display a video frame.
static void display(void *data, void *id) {
//Data to be displayed
}
VideoVlcComponent::VideoVlcComponent(Window* window) :
VideoComponent(window),
mMediaPlayer(nullptr)
{
memset(&mContext, 0, sizeof(mContext));
// Get an empty texture for rendering the video
mTexture = TextureResource::get("");
// Make sure VLC has been initialised
setupVLC();
}
VideoVlcComponent::~VideoVlcComponent()
{
}
void VideoVlcComponent::setResize(float width, float height)
{
mTargetSize << width, height;
mTargetIsMax = false;
mStaticImage.setResize(width, height);
resize();
}
void VideoVlcComponent::setMaxSize(float width, float height)
{
mTargetSize << width, height;
mTargetIsMax = true;
mStaticImage.setMaxSize(width, height);
resize();
}
void VideoVlcComponent::resize()
{
if(!mTexture)
return;
const Eigen::Vector2f textureSize(mVideoWidth, mVideoHeight);
if(textureSize.isZero())
return;
// SVG rasterization is determined by height (see SVGResource.cpp), and rasterization is done in terms of pixels
// 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!
if(mTargetIsMax)
{
mSize = textureSize;
Eigen::Vector2f resizeScale((mTargetSize.x() / mSize.x()), (mTargetSize.y() / mSize.y()));
if(resizeScale.x() < resizeScale.y())
{
mSize[0] *= resizeScale.x();
mSize[1] *= resizeScale.x();
}else{
mSize[0] *= resizeScale.y();
mSize[1] *= resizeScale.y();
}
// for SVG rasterization, always calculate width from rounded height (see comment above)
mSize[1] = round(mSize[1]);
mSize[0] = (mSize[1] / textureSize.y()) * textureSize.x();
}else{
// if both components are set, we just stretch
// if no components are set, we don't resize at all
mSize = mTargetSize.isZero() ? 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] = round(mTargetSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}else if(mTargetSize.x() && !mTargetSize.y())
{
mSize[1] = round((mTargetSize.x() / textureSize.x()) * textureSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}
}
// mSize.y() should already be rounded
mTexture->rasterizeAt((int)round(mSize.x()), (int)round(mSize.y()));
onSizeChanged();
}
void VideoVlcComponent::render(const Eigen::Affine3f& parentTrans)
{
VideoComponent::render(parentTrans);
float x, y;
Eigen::Affine3f trans = parentTrans * getTransform();
GuiComponent::renderChildren(trans);
Renderer::setMatrix(trans);
if (mIsPlaying && mContext.valid)
{
float tex_offs_x = 0.0f;
float tex_offs_y = 0.0f;
float x2;
float y2;
x = -(float)mSize.x() * mOrigin.x();
y = -(float)mSize.y() * mOrigin.y();
x2 = x+mSize.x();
y2 = y+mSize.y();
// Define a structure to contain the data for each vertex
struct Vertex
{
Eigen::Vector2f pos;
Eigen::Vector2f tex;
Eigen::Vector4f colour;
} vertices[6];
// We need two triangles to cover the rectangular area
vertices[0].pos[0] = x; vertices[0].pos[1] = y;
vertices[1].pos[0] = x; vertices[1].pos[1] = y2;
vertices[2].pos[0] = x2; vertices[2].pos[1] = y;
vertices[3].pos[0] = x2; vertices[3].pos[1] = y;
vertices[4].pos[0] = x; vertices[4].pos[1] = y2;
vertices[5].pos[0] = x2; vertices[5].pos[1] = y2;
// Texture coordinates
vertices[0].tex[0] = -tex_offs_x; vertices[0].tex[1] = -tex_offs_y;
vertices[1].tex[0] = -tex_offs_x; vertices[1].tex[1] = 1.0f + tex_offs_y;
vertices[2].tex[0] = 1.0f + tex_offs_x; vertices[2].tex[1] = -tex_offs_y;
vertices[3].tex[0] = 1.0f + tex_offs_x; vertices[3].tex[1] = -tex_offs_y;
vertices[4].tex[0] = -tex_offs_x; vertices[4].tex[1] = 1.0f + tex_offs_y;
vertices[5].tex[0] = 1.0f + tex_offs_x; vertices[5].tex[1] = 1.0f + tex_offs_y;
// Colours - use this to fade the video in and out
for (int i = 0; i < (4 * 6); ++i) {
if ((i%4) < 3)
vertices[i / 4].colour[i % 4] = mFadeIn;
else
vertices[i / 4].colour[i % 4] = 1.0f;
}
glEnable(GL_TEXTURE_2D);
// Build a texture for the video frame
mTexture->initFromPixels((unsigned char*)mContext.surface->pixels, mContext.surface->w, mContext.surface->h);
mTexture->bind();
// Render it
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glColorPointer(4, GL_FLOAT, sizeof(Vertex), &vertices[0].colour);
glVertexPointer(2, GL_FLOAT, sizeof(Vertex), &vertices[0].pos);
glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &vertices[0].tex);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisable(GL_TEXTURE_2D);
}
}
void VideoVlcComponent::setupContext()
{
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();
}
}
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", "", "", "" };
// check if we want to mute the audio
mVLC = libvlc_new(sizeof(args) / sizeof(args[0]), args);
}
}
void VideoVlcComponent::handleLooping()
{
if (mIsPlaying && mMediaPlayer)
{
libvlc_state_t state = libvlc_media_player_get_state(mMediaPlayer);
if (state == libvlc_Ended)
{
//libvlc_media_player_set_position(mMediaPlayer, 0.0f);
libvlc_media_player_set_media(mMediaPlayer, mMedia);
libvlc_media_player_play(mMediaPlayer);
}
}
}
void VideoVlcComponent::startVideo()
{
if (!mIsPlaying) {
mVideoWidth = 0;
mVideoHeight = 0;
#ifdef WIN32
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> wton;
std::string path = wton.to_bytes(mVideoPath.c_str());
#else
std::string path(mVideoPath.c_str());
#endif
// Make sure we have a video path
if (mVLC && (path.size() > 0))
{
// Set the video that we are going to be playing so we don't attempt to restart it
mPlayingVideoPath = mVideoPath;
// Open the media
mMedia = libvlc_media_new_path(mVLC, path.c_str());
if (mMedia)
{
unsigned track_count;
// Get the media metadata so we can find the aspect ratio
libvlc_media_parse(mMedia);
libvlc_media_track_t** tracks;
track_count = libvlc_media_tracks_get(mMedia, &tracks);
for (unsigned track = 0; track < track_count; ++track)
{
if (tracks[track]->i_type == libvlc_track_video)
{
mVideoWidth = tracks[track]->video->i_width;
mVideoHeight = tracks[track]->video->i_height;
break;
}
}
libvlc_media_tracks_release(tracks, track_count);
// Make sure we found a valid video track
if ((mVideoWidth > 0) && (mVideoHeight > 0))
{
setupContext();
// Setup the media player
mMediaPlayer = libvlc_media_player_new_from_media(mMedia);
if (!Settings::getInstance()->getBool("VideoAudio"))
{
libvlc_audio_set_mute(mMediaPlayer, 1);
}
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);
// Update the playing state
mIsPlaying = true;
mFadeIn = 0.0f;
}
}
}
}
}
void VideoVlcComponent::stopVideo()
{
mIsPlaying = false;
mStartDelayed = false;
// Release the media player so it stops calling back to us
if (mMediaPlayer)
{
libvlc_media_player_stop(mMediaPlayer);
libvlc_media_player_release(mMediaPlayer);
libvlc_media_release(mMedia);
mMediaPlayer = NULL;
freeContext();
}
}

View file

@ -0,0 +1,70 @@
#ifndef _VIDEOVLCCOMPONENT_H_
#define _VIDEOVLCCOMPONENT_H_
#include "platform.h"
#include GLHEADER
#include "VideoComponent.h"
#include <vlc/vlc.h>
#include "resources/TextureResource.h"
struct VideoContext {
SDL_Surface* surface;
SDL_mutex* mutex;
bool valid;
};
class VideoVlcComponent : public VideoComponent
{
// Structure that groups together the configuration of the video component
struct Configuration
{
unsigned startDelay;
bool showSnapshotNoVideo;
bool showSnapshotDelay;
std::string defaultVideoPath;
};
public:
static void setupVLC();
VideoVlcComponent(Window* window);
virtual ~VideoVlcComponent();
void render(const Eigen::Affine3f& 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.
// Can be set before or after a video is loaded.
// setMaxSize() and setResize() are mutually exclusive.
void setResize(float width, float height);
// Resize the video to be as large as possible but fit within a box of this size.
// 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);
private:
// Calculates the correct mSize from our resizing information (set by setResize/setMaxSize).
// Used internally whenever the resizing parameters or texture change.
void resize();
// Start the video Immediately
virtual void startVideo();
// Stop the video
virtual void stopVideo();
// Handle looping the video. Must be called periodically
virtual void handleLooping();
void setupContext();
void freeContext();
private:
static libvlc_instance_t* mVLC;
libvlc_media_t* mMedia;
libvlc_media_player_t* mMediaPlayer;
VideoContext mContext;
std::shared_ptr<TextureResource> mTexture;
};
#endif