Add video view that is based on detail view but allows themes to include a video preview of the selected game along with a marquee image

This commit is contained in:
fieldofcows 2016-12-04 23:47:34 +00:00
parent 79cce69771
commit 25e1067794
20 changed files with 1318 additions and 13 deletions

View file

@ -0,0 +1,55 @@
# - Try to find VLC library
# Once done this will define
#
# VLC_FOUND - system has VLC
# VLC_INCLUDE_DIR - The VLC include directory
# VLC_LIBRARIES - The libraries needed to use VLC
# VLC_DEFINITIONS - Compiler switches required for using VLC
#
# Copyright (C) 2008, Tanguy Krotoff <tkrotoff@gmail.com>
# Copyright (C) 2008, Lukas Durfina <lukas.durfina@gmail.com>
# Copyright (c) 2009, Fathi Boudra <fboudra@gmail.com>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#
if(VLC_INCLUDE_DIR AND VLC_LIBRARIES)
# in cache already
set(VLC_FIND_QUIETLY TRUE)
endif(VLC_INCLUDE_DIR AND VLC_LIBRARIES)
# use pkg-config to get the directories and then use these values
# in the FIND_PATH() and FIND_LIBRARY() calls
if(NOT WIN32)
find_package(PkgConfig)
pkg_check_modules(VLC libvlc>=1.0.0)
set(VLC_DEFINITIONS ${VLC_CFLAGS})
set(VLC_LIBRARIES ${VLC_LDFLAGS})
endif(NOT WIN32)
# TODO add argument support to pass version on find_package
include(MacroEnsureVersion)
macro_ensure_version(1.0.0 ${VLC_VERSION} VLC_VERSION_OK)
if(VLC_VERSION_OK)
set(VLC_FOUND TRUE)
message(STATUS "VLC library found")
else(VLC_VERSION_OK)
set(VLC_FOUND FALSE)
message(FATAL_ERROR "VLC library not found")
endif(VLC_VERSION_OK)
find_path(VLC_INCLUDE_DIR
NAMES vlc.h
PATHS ${VLC_INCLUDE_DIRS}
PATH_SUFFIXES vlc)
find_library(VLC_LIBRARIES
NAMES vlc
PATHS ${VLC_LIBRARY_DIRS})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(VLC DEFAULT_MSG VLC_INCLUDE_DIR VLC_LIBRARIES)
# show the VLC_INCLUDE_DIR and VLC_LIBRARIES variables only in the advanced view
mark_as_advanced(VLC_INCLUDE_DIR VLC_LIBRARIES)

View file

@ -0,0 +1,117 @@
# This file defines the following macros for developers to use in ensuring
# that installed software is of the right version:
#
# MACRO_ENSURE_VERSION - test that a version number is greater than
# or equal to some minimum
# MACRO_ENSURE_VERSION_RANGE - test that a version number is greater than
# or equal to some minimum and less than some
# maximum
# MACRO_ENSURE_VERSION2 - deprecated, do not use in new code
#
# MACRO_ENSURE_VERSION
# This macro compares version numbers of the form "x.y.z" or "x.y"
# MACRO_ENSURE_VERSION( FOO_MIN_VERSION FOO_VERSION_FOUND FOO_VERSION_OK)
# will set FOO_VERSION_OK to true if FOO_VERSION_FOUND >= FOO_MIN_VERSION
# Leading and trailing text is ok, e.g.
# MACRO_ENSURE_VERSION( "2.5.31" "flex 2.5.4a" VERSION_OK)
# which means 2.5.31 is required and "flex 2.5.4a" is what was found on the system
# Copyright (c) 2006, David Faure, <faure@kde.org>
# Copyright (c) 2007, Will Stephenson <wstephenson@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
# MACRO_ENSURE_VERSION_RANGE
# This macro ensures that a version number of the form
# "x.y.z" or "x.y" falls within a range defined by
# min_version <= found_version < max_version.
# If this expression holds, FOO_VERSION_OK will be set TRUE
#
# Example: MACRO_ENSURE_VERSION_RANGE3( "0.1.0" ${FOOCODE_VERSION} "0.7.0" FOO_VERSION_OK )
#
# This macro will break silently if any of x,y,z are greater than 100.
#
# Copyright (c) 2007, Will Stephenson <wstephenson@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
# NORMALIZE_VERSION
# Helper macro to convert version numbers of the form "x.y.z"
# to an integer equal to 10^4 * x + 10^2 * y + z
#
# This macro will break silently if any of x,y,z are greater than 100.
#
# Copyright (c) 2006, David Faure, <faure@kde.org>
# Copyright (c) 2007, Will Stephenson <wstephenson@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
# CHECK_RANGE_INCLUSIVE_LOWER
# Helper macro to check whether x <= y < z
#
# Copyright (c) 2007, Will Stephenson <wstephenson@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
MACRO(NORMALIZE_VERSION _requested_version _normalized_version)
STRING(REGEX MATCH "[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" _threePartMatch "${_requested_version}")
if (_threePartMatch)
# parse the parts of the version string
STRING(REGEX REPLACE "[^0-9]*([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" _major_vers "${_requested_version}")
STRING(REGEX REPLACE "[^0-9]*[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" _minor_vers "${_requested_version}")
STRING(REGEX REPLACE "[^0-9]*[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" _patch_vers "${_requested_version}")
else (_threePartMatch)
STRING(REGEX REPLACE "([0-9]+)\\.[0-9]+" "\\1" _major_vers "${_requested_version}")
STRING(REGEX REPLACE "[0-9]+\\.([0-9]+)" "\\1" _minor_vers "${_requested_version}")
set(_patch_vers "0")
endif (_threePartMatch)
# compute an overall version number which can be compared at once
MATH(EXPR ${_normalized_version} "${_major_vers}*10000 + ${_minor_vers}*100 + ${_patch_vers}")
ENDMACRO(NORMALIZE_VERSION)
MACRO(MACRO_CHECK_RANGE_INCLUSIVE_LOWER _lower_limit _value _upper_limit _ok)
if (${_value} LESS ${_lower_limit})
set( ${_ok} FALSE )
elseif (${_value} EQUAL ${_lower_limit})
set( ${_ok} TRUE )
elseif (${_value} EQUAL ${_upper_limit})
set( ${_ok} FALSE )
elseif (${_value} GREATER ${_upper_limit})
set( ${_ok} FALSE )
else (${_value} LESS ${_lower_limit})
set( ${_ok} TRUE )
endif (${_value} LESS ${_lower_limit})
ENDMACRO(MACRO_CHECK_RANGE_INCLUSIVE_LOWER)
MACRO(MACRO_ENSURE_VERSION requested_version found_version var_too_old)
NORMALIZE_VERSION( ${requested_version} req_vers_num )
NORMALIZE_VERSION( ${found_version} found_vers_num )
if (found_vers_num LESS req_vers_num)
set( ${var_too_old} FALSE )
else (found_vers_num LESS req_vers_num)
set( ${var_too_old} TRUE )
endif (found_vers_num LESS req_vers_num)
ENDMACRO(MACRO_ENSURE_VERSION)
MACRO(MACRO_ENSURE_VERSION2 requested_version2 found_version2 var_too_old2)
MACRO_ENSURE_VERSION( ${requested_version2} ${found_version2} ${var_too_old2})
ENDMACRO(MACRO_ENSURE_VERSION2)
MACRO(MACRO_ENSURE_VERSION_RANGE min_version found_version max_version var_ok)
NORMALIZE_VERSION( ${min_version} req_vers_num )
NORMALIZE_VERSION( ${found_version} found_vers_num )
NORMALIZE_VERSION( ${max_version} max_vers_num )
MACRO_CHECK_RANGE_INCLUSIVE_LOWER( ${req_vers_num} ${found_vers_num} ${max_vers_num} ${var_ok})
ENDMACRO(MACRO_ENSURE_VERSION_RANGE)

View file

@ -48,6 +48,7 @@ find_package(SDL2 REQUIRED)
find_package(Boost REQUIRED COMPONENTS system filesystem date_time locale) find_package(Boost REQUIRED COMPONENTS system filesystem date_time locale)
find_package(Eigen3 REQUIRED) find_package(Eigen3 REQUIRED)
find_package(CURL REQUIRED) find_package(CURL REQUIRED)
find_package(VLC REQUIRED)
#add ALSA for Linux #add ALSA for Linux
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
@ -99,6 +100,7 @@ set(COMMON_INCLUDE_DIRS
${Boost_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}
${EIGEN3_INCLUDE_DIR} ${EIGEN3_INCLUDE_DIR}
${CURL_INCLUDE_DIR} ${CURL_INCLUDE_DIR}
${VLC_INCLUDE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/external ${CMAKE_CURRENT_SOURCE_DIR}/external
${CMAKE_CURRENT_SOURCE_DIR}/es-core/src ${CMAKE_CURRENT_SOURCE_DIR}/es-core/src
) )
@ -148,6 +150,7 @@ set(COMMON_LIBRARIES
${FreeImage_LIBRARIES} ${FreeImage_LIBRARIES}
${SDL2_LIBRARY} ${SDL2_LIBRARY}
${CURL_LIBRARIES} ${CURL_LIBRARIES}
${VLC_LIBRARIES}
pugixml pugixml
nanosvg nanosvg
) )

View file

@ -41,7 +41,10 @@ EmulationStation has a few dependencies. For building, you'll need CMake, SDL2,
**On Debian/Ubuntu:** **On Debian/Ubuntu:**
All of this be easily installed with apt-get: All of this be easily installed with apt-get:
```bash ```bash
sudo apt-get install libsdl2-dev libboost-system-dev libboost-filesystem-dev libboost-date-time-dev libboost-locale-dev libfreeimage-dev libfreetype6-dev libeigen3-dev libcurl4-openssl-dev libasound2-dev libgl1-mesa-dev build-essential cmake fonts-droid sudo apt-get install libsdl2-dev libboost-system-dev libboost-filesystem-dev libboost-date-time-dev \
libboost-locale-dev libfreeimage-dev libfreetype6-dev libeigen3-dev libcurl4-openssl-dev \
libasound2-dev libgl1-mesa-dev build-essential cmake fonts-droid \
libvlc-dev libvlccore-dev vlc-nox
``` ```
Then, generate and build the Makefile with CMake: Then, generate and build the Makefile with CMake:

View file

@ -38,6 +38,7 @@ set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.h
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.h
@ -84,6 +85,7 @@ set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/IGameListView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/ISimpleGameListView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/GridGameListView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/gamelist/VideoGameListView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/SystemView.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/views/ViewController.cpp
) )

View file

@ -81,6 +81,32 @@ const std::string& FileData::getThumbnailPath() const
return metadata.get("image"); return metadata.get("image");
} }
const std::string& FileData::getVideoPath() const
{
if (mType == GAME)
{
return metadata.get("video");
}
else
{
static std::string empty;
return empty;
}
}
const std::string& FileData::getMarqueePath() const
{
if (mType == GAME)
{
return metadata.get("marquee");
}
else
{
static std::string empty;
return empty;
}
}
std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask) const std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask) const
{ {

View file

@ -45,6 +45,8 @@ public:
inline SystemData* getSystem() const { return mSystem; } inline SystemData* getSystem() const { return mSystem; }
virtual const std::string& getThumbnailPath() const; virtual const std::string& getThumbnailPath() const;
virtual const std::string& getVideoPath() const;
virtual const std::string& getMarqueePath() const;
std::vector<FileData*> getFilesRecursive(unsigned int typeMask) const; std::vector<FileData*> getFilesRecursive(unsigned int typeMask) const;

View file

@ -9,8 +9,10 @@ MetaDataDecl gameDecls[] = {
// key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd // key, type, default, statistic, name in GuiMetaDataEd, prompt in GuiMetaDataEd
{"name", MD_STRING, "", false, "name", "enter game name"}, {"name", MD_STRING, "", false, "name", "enter game name"},
{"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"}, {"desc", MD_MULTILINE_STRING, "", false, "description", "enter description"},
{"image", MD_IMAGE_PATH, "", false, "image", "enter path to image"}, {"image", MD_PATH, "", false, "image", "enter path to image"},
{"thumbnail", MD_IMAGE_PATH, "", false, "thumbnail", "enter path to thumbnail"}, {"video", MD_PATH , "", false, "video", "enter path to video"},
{"marquee", MD_PATH, "", false, "marquee", "enter path to marquee"},
{"thumbnail", MD_PATH, "", false, "thumbnail", "enter path to thumbnail"},
{"rating", MD_RATING, "0.000000", false, "rating", "enter rating"}, {"rating", MD_RATING, "0.000000", false, "rating", "enter rating"},
{"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"}, {"releasedate", MD_DATE, "not-a-date-time", false, "release date", "enter release date"},
{"developer", MD_STRING, "unknown", false, "developer", "enter game developer"}, {"developer", MD_STRING, "unknown", false, "developer", "enter game developer"},
@ -25,8 +27,8 @@ const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls + sizeof(gameDecls)
MetaDataDecl folderDecls[] = { MetaDataDecl folderDecls[] = {
{"name", MD_STRING, "", false}, {"name", MD_STRING, "", false},
{"desc", MD_MULTILINE_STRING, "", false}, {"desc", MD_MULTILINE_STRING, "", false},
{"image", MD_IMAGE_PATH, "", false}, {"image", MD_PATH, "", false},
{"thumbnail", MD_IMAGE_PATH, "", false}, {"thumbnail", MD_PATH, "", false},
}; };
const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls + sizeof(folderDecls) / sizeof(folderDecls[0])); const std::vector<MetaDataDecl> folderMDD(folderDecls, folderDecls + sizeof(folderDecls) / sizeof(folderDecls[0]));
@ -68,9 +70,10 @@ MetaDataList MetaDataList::createFromXML(MetaDataListType type, pugi::xml_node n
{ {
// if it's a path, resolve relative paths // if it's a path, resolve relative paths
std::string value = md.text().get(); std::string value = md.text().get();
if(iter->type == MD_IMAGE_PATH) if (iter->type == MD_PATH)
{
value = resolvePath(value, relativeTo, true).generic_string(); value = resolvePath(value, relativeTo, true).generic_string();
}
mdl.set(iter->key, value); mdl.set(iter->key, value);
}else{ }else{
mdl.set(iter->key, iter->defaultValue); mdl.set(iter->key, iter->defaultValue);
@ -96,7 +99,7 @@ void MetaDataList::appendToXML(pugi::xml_node parent, bool ignoreDefaults, const
// try and make paths relative if we can // try and make paths relative if we can
std::string value = mapIter->second; std::string value = mapIter->second;
if(mddIter->type == MD_IMAGE_PATH) if (mddIter->type == MD_PATH)
value = makeRelativePath(value, relativeTo, true).generic_string(); value = makeRelativePath(value, relativeTo, true).generic_string();
parent.append_child(mapIter->first.c_str()).text().set(value.c_str()); parent.append_child(mapIter->first.c_str()).text().set(value.c_str());

View file

@ -16,7 +16,7 @@ enum MetaDataType
//specialized types //specialized types
MD_MULTILINE_STRING, MD_MULTILINE_STRING,
MD_IMAGE_PATH, MD_PATH,
MD_RATING, MD_RATING,
MD_DATE, MD_DATE,
MD_TIME //used for lastplayed MD_TIME //used for lastplayed

View file

@ -5,12 +5,14 @@
#include "views/gamelist/BasicGameListView.h" #include "views/gamelist/BasicGameListView.h"
#include "views/gamelist/DetailedGameListView.h" #include "views/gamelist/DetailedGameListView.h"
#include "views/gamelist/VideoGameListView.h"
#include "views/gamelist/GridGameListView.h" #include "views/gamelist/GridGameListView.h"
#include "guis/GuiMenu.h" #include "guis/GuiMenu.h"
#include "guis/GuiMsgBox.h" #include "guis/GuiMsgBox.h"
#include "animations/LaunchAnimation.h" #include "animations/LaunchAnimation.h"
#include "animations/MoveCameraAnimation.h" #include "animations/MoveCameraAnimation.h"
#include "animations/LambdaAnimation.h" #include "animations/LambdaAnimation.h"
#include <SDL2/SDL.h>
ViewController* ViewController::sInstance = NULL; ViewController* ViewController::sInstance = NULL;
@ -55,6 +57,12 @@ int ViewController::getSystemId(SystemData* system)
void ViewController::goToSystemView(SystemData* system) void ViewController::goToSystemView(SystemData* system)
{ {
// Tell any current view it's about to be hidden
if (mCurrentView)
{
mCurrentView->onHide();
}
mState.viewing = SYSTEM_SELECT; mState.viewing = SYSTEM_SELECT;
mState.system = system; mState.system = system;
@ -99,7 +107,15 @@ void ViewController::goToGameList(SystemData* system)
mState.viewing = GAME_LIST; mState.viewing = GAME_LIST;
mState.system = system; mState.system = system;
if (mCurrentView)
{
mCurrentView->onHide();
}
mCurrentView = getGameListView(system); mCurrentView = getGameListView(system);
if (mCurrentView)
{
mCurrentView->onShow();
}
playViewTransition(); playViewTransition();
} }
@ -163,6 +179,10 @@ void ViewController::launch(FileData* game, Eigen::Vector3f center)
return; return;
} }
// Hide the current view
if (mCurrentView)
mCurrentView->onHide();
Eigen::Affine3f origCamera = mCamera; Eigen::Affine3f origCamera = mCamera;
origCamera.translation() = -mCurrentView->getPosition(); origCamera.translation() = -mCurrentView->getPosition();
@ -210,17 +230,26 @@ std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* syste
//decide type //decide type
bool detailed = false; bool detailed = false;
bool video = false;
std::vector<FileData*> files = system->getRootFolder()->getFilesRecursive(GAME | FOLDER); std::vector<FileData*> files = system->getRootFolder()->getFilesRecursive(GAME | FOLDER);
for(auto it = files.begin(); it != files.end(); it++) for(auto it = files.begin(); it != files.end(); it++)
{ {
if(!(*it)->getThumbnailPath().empty()) if(!(*it)->getVideoPath().empty())
{
video = true;
break;
}
else if(!(*it)->getThumbnailPath().empty())
{ {
detailed = true; detailed = true;
break; // Don't break out in case any subsequent files have video
} }
} }
if(detailed) if (video)
// Create the view
view = std::shared_ptr<IGameListView>(new VideoGameListView(mWindow, system->getRootFolder()));
else if(detailed)
view = std::shared_ptr<IGameListView>(new DetailedGameListView(mWindow, system->getRootFolder())); view = std::shared_ptr<IGameListView>(new DetailedGameListView(mWindow, system->getRootFolder()));
else else
view = std::shared_ptr<IGameListView>(new BasicGameListView(mWindow, system->getRootFolder())); view = std::shared_ptr<IGameListView>(new BasicGameListView(mWindow, system->getRootFolder()));
@ -347,6 +376,10 @@ void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
break; break;
} }
} }
// Redisplay the current view
if (mCurrentView)
mCurrentView->onShow();
} }
void ViewController::reloadAll() void ViewController::reloadAll()

View file

@ -0,0 +1,345 @@
#include "views/gamelist/VideoGameListView.h"
#include "views/ViewController.h"
#include "Window.h"
#include "animations/LambdaAnimation.h"
#include <sys/stat.h>
#include <fcntl.h>
VideoGameListView::VideoGameListView(Window* window, FileData* root) :
BasicGameListView(window, root),
mDescContainer(window), mDescription(window),
mMarquee(window),
mImage(window),
mVideo(window),
mVideoPlaying(false),
mLblRating(window), mLblReleaseDate(window), mLblDeveloper(window), mLblPublisher(window),
mLblGenre(window), mLblPlayers(window), mLblLastPlayed(window), mLblPlayCount(window),
mRating(window), mReleaseDate(window), mDeveloper(window), mPublisher(window),
mGenre(window), mPlayers(window), mLastPlayed(window), mPlayCount(window)
{
const float padding = 0.01f;
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);
mList.setCursorChangedCallback([&](const CursorState& state) { updateInfoPanel(); });
// Marquee
mMarquee.setOrigin(0.5f, 0.5f);
mMarquee.setPosition(mSize.x() * 0.25f, mSize.y() * 0.10f);
mMarquee.setMaxSize(mSize.x() * (0.5f - 2*padding), mSize.y() * 0.18f);
addChild(&mMarquee);
// Image
mImage.setOrigin(0.5f, 0.5f);
// Default to off the screen
mImage.setPosition(2.0f, 2.0f);
mImage.setMaxSize(1.0f, 1.0f);
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);
addChild(&mVideo);
// We want the video to be in front of the background but behind any 'extra' images
for (std::vector<GuiComponent*>::iterator it = mChildren.begin(); it != mChildren.end(); ++it)
{
if (*it == &mThemeExtras)
{
mChildren.insert(it, &mVideo);
mChildren.pop_back();
break;
}
}
// metadata labels + values
mLblRating.setText("Rating: ");
addChild(&mLblRating);
addChild(&mRating);
mLblReleaseDate.setText("Released: ");
addChild(&mLblReleaseDate);
addChild(&mReleaseDate);
mLblDeveloper.setText("Developer: ");
addChild(&mLblDeveloper);
addChild(&mDeveloper);
mLblPublisher.setText("Publisher: ");
addChild(&mLblPublisher);
addChild(&mPublisher);
mLblGenre.setText("Genre: ");
addChild(&mLblGenre);
addChild(&mGenre);
mLblPlayers.setText("Players: ");
addChild(&mLblPlayers);
addChild(&mPlayers);
mLblLastPlayed.setText("Last played: ");
addChild(&mLblLastPlayed);
mLastPlayed.setDisplayMode(DateTimeComponent::DISP_RELATIVE_TO_NOW);
addChild(&mLastPlayed);
mLblPlayCount.setText("Times played: ");
addChild(&mLblPlayCount);
addChild(&mPlayCount);
mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f);
mDescContainer.setSize(mSize.x() * (0.50f - 2*padding), mSize.y() - mDescContainer.getPosition().y());
mDescContainer.setAutoScroll(true);
addChild(&mDescContainer);
mDescription.setFont(Font::get(FONT_SIZE_SMALL));
mDescription.setSize(mDescContainer.getSize().x(), 0);
mDescContainer.addChild(&mDescription);
initMDLabels();
initMDValues();
updateInfoPanel();
}
VideoGameListView::~VideoGameListView()
{
}
void VideoGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
{
BasicGameListView::onThemeChanged(theme);
using namespace ThemeFlags;
mMarquee.applyTheme(theme, getName(), "md_marquee", POSITION | ThemeFlags::SIZE);
mImage.applyTheme(theme, getName(), "md_image", POSITION | ThemeFlags::SIZE);
mVideo.applyTheme(theme, getName(), "md_video", POSITION | ThemeFlags::SIZE | ThemeFlags::DELAY);
initMDLabels();
std::vector<TextComponent*> labels = getMDLabels();
assert(labels.size() == 8);
const char* lblElements[8] = {
"md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher",
"md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount"
};
for(unsigned int i = 0; i < labels.size(); i++)
{
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
}
initMDValues();
std::vector<GuiComponent*> values = getMDValues();
assert(values.size() == 8);
const char* valElements[8] = {
"md_rating", "md_releasedate", "md_developer", "md_publisher",
"md_genre", "md_players", "md_lastplayed", "md_playcount"
};
for(unsigned int i = 0; i < values.size(); i++)
{
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
}
mDescContainer.applyTheme(theme, getName(), "md_description", POSITION | ThemeFlags::SIZE);
mDescription.setSize(mDescContainer.getSize().x(), 0);
mDescription.applyTheme(theme, getName(), "md_description", ALL ^ (POSITION | ThemeFlags::SIZE | TEXT));
}
void VideoGameListView::initMDLabels()
{
using namespace Eigen;
std::vector<TextComponent*> components = getMDLabels();
const unsigned int colCount = 2;
const unsigned int rowCount = components.size() / 2;
Vector3f start(mSize.x() * 0.01f, mSize.y() * 0.625f, 0.0f);
const float colSize = (mSize.x() * 0.48f) / colCount;
const float rowPadding = 0.01f * mSize.y();
for(unsigned int i = 0; i < components.size(); i++)
{
const unsigned int row = i % rowCount;
Vector3f pos(0.0f, 0.0f, 0.0f);
if(row == 0)
{
pos = start + Vector3f(colSize * (i / rowCount), 0, 0);
}else{
// work from the last component
GuiComponent* lc = components[i-1];
pos = lc->getPosition() + Vector3f(0, lc->getSize().y() + rowPadding, 0);
}
components[i]->setFont(Font::get(FONT_SIZE_SMALL));
components[i]->setPosition(pos);
}
}
void VideoGameListView::initMDValues()
{
using namespace Eigen;
std::vector<TextComponent*> labels = getMDLabels();
std::vector<GuiComponent*> values = getMDValues();
std::shared_ptr<Font> defaultFont = Font::get(FONT_SIZE_SMALL);
mRating.setSize(defaultFont->getHeight() * 5.0f, (float)defaultFont->getHeight());
mReleaseDate.setFont(defaultFont);
mDeveloper.setFont(defaultFont);
mPublisher.setFont(defaultFont);
mGenre.setFont(defaultFont);
mPlayers.setFont(defaultFont);
mLastPlayed.setFont(defaultFont);
mPlayCount.setFont(defaultFont);
float bottom = 0.0f;
const float colSize = (mSize.x() * 0.48f) / 2;
for(unsigned int i = 0; i < labels.size(); i++)
{
const float heightDiff = (labels[i]->getSize().y() - values[i]->getSize().y()) / 2;
values[i]->setPosition(labels[i]->getPosition() + Vector3f(labels[i]->getSize().x(), heightDiff, 0));
values[i]->setSize(colSize - labels[i]->getSize().x(), values[i]->getSize().y());
float testBot = values[i]->getPosition().y() + values[i]->getSize().y();
if(testBot > bottom)
bottom = testBot;
}
mDescContainer.setPosition(mDescContainer.getPosition().x(), bottom + mSize.y() * 0.01f);
mDescContainer.setSize(mDescContainer.getSize().x(), mSize.y() - mDescContainer.getPosition().y());
}
void VideoGameListView::updateInfoPanel()
{
FileData* file = (mList.size() == 0 || mList.isScrolling()) ? NULL : mList.getSelected();
bool fadingOut;
if(file == NULL)
{
mVideo.setVideo("");
mVideo.setImage("");
mVideoPlaying = false;
//mMarquee.setImage("");
//mDescription.setText("");
fadingOut = true;
}else{
std::string video_path;
std::string marquee_path;
std::string thumbnail_path;
video_path = file->getVideoPath();
marquee_path = file->getMarqueePath();
thumbnail_path = file->getThumbnailPath();
if (!video_path.empty() && (video_path[0] == '~'))
{
video_path.erase(0, 1);
video_path.insert(0, getHomePath());
}
if (!marquee_path.empty() && (marquee_path[0] == '~'))
{
marquee_path.erase(0, 1);
marquee_path.insert(0, getHomePath());
}
if (!thumbnail_path.empty() && (thumbnail_path[0] == '~'))
{
thumbnail_path.erase(0, 1);
thumbnail_path.insert(0, getHomePath());
}
if (!mVideo.setVideo(video_path))
mVideo.setDefaultVideo();
mVideoPlaying = true;
mVideo.setImage(thumbnail_path);
mMarquee.setImage(marquee_path);
mImage.setImage(thumbnail_path);
mDescription.setText(file->metadata.get("desc"));
mDescContainer.reset();
if(file->getType() == GAME)
{
mRating.setValue(file->metadata.get("rating"));
mReleaseDate.setValue(file->metadata.get("releasedate"));
mDeveloper.setValue(file->metadata.get("developer"));
mPublisher.setValue(file->metadata.get("publisher"));
mGenre.setValue(file->metadata.get("genre"));
mPlayers.setValue(file->metadata.get("players"));
mLastPlayed.setValue(file->metadata.get("lastplayed"));
mPlayCount.setValue(file->metadata.get("playcount"));
}
fadingOut = false;
}
std::vector<GuiComponent*> comps = getMDValues();
comps.push_back(&mMarquee);
comps.push_back(&mVideo);
comps.push_back(&mDescription);
comps.push_back(&mImage);
std::vector<TextComponent*> labels = getMDLabels();
comps.insert(comps.end(), labels.begin(), labels.end());
for(auto it = comps.begin(); it != comps.end(); it++)
{
GuiComponent* comp = *it;
// an animation is playing
// then animate if reverse != fadingOut
// an animation is not playing
// then animate if opacity != our target opacity
if((comp->isAnimationPlaying(0) && comp->isAnimationReversed(0) != fadingOut) ||
(!comp->isAnimationPlaying(0) && comp->getOpacity() != (fadingOut ? 0 : 255)))
{
auto func = [comp](float t)
{
comp->setOpacity((unsigned char)(lerp<float>(0.0f, 1.0f, t)*255));
};
comp->setAnimation(new LambdaAnimation(func, 150), 0, nullptr, fadingOut);
}
}
}
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;
ViewController::get()->launch(game, target);
}
std::vector<TextComponent*> VideoGameListView::getMDLabels()
{
std::vector<TextComponent*> ret;
ret.push_back(&mLblRating);
ret.push_back(&mLblReleaseDate);
ret.push_back(&mLblDeveloper);
ret.push_back(&mLblPublisher);
ret.push_back(&mLblGenre);
ret.push_back(&mLblPlayers);
ret.push_back(&mLblLastPlayed);
ret.push_back(&mLblPlayCount);
return ret;
}
std::vector<GuiComponent*> VideoGameListView::getMDValues()
{
std::vector<GuiComponent*> ret;
ret.push_back(&mRating);
ret.push_back(&mReleaseDate);
ret.push_back(&mDeveloper);
ret.push_back(&mPublisher);
ret.push_back(&mGenre);
ret.push_back(&mPlayers);
ret.push_back(&mLastPlayed);
ret.push_back(&mPlayCount);
return ret;
}
void VideoGameListView::update(int deltaTime)
{
BasicGameListView::update(deltaTime);
mVideo.update(deltaTime);
}

View file

@ -0,0 +1,53 @@
#pragma once
#include "views/gamelist/BasicGameListView.h"
#include "components/ScrollableContainer.h"
#include "components/RatingComponent.h"
#include "components/DateTimeComponent.h"
#include "components/VideoComponent.h"
class VideoGameListView : public BasicGameListView
{
public:
VideoGameListView(Window* window, FileData* root);
virtual ~VideoGameListView();
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
virtual const char* getName() const override { return "video"; }
protected:
virtual void launch(FileData* game) override;
virtual void update(int deltaTime) override;
private:
void updateInfoPanel();
void initMDLabels();
void initMDValues();
ImageComponent mMarquee;
VideoComponent mVideo;
ImageComponent mImage;
TextComponent mLblRating, mLblReleaseDate, mLblDeveloper, mLblPublisher, mLblGenre, mLblPlayers, mLblLastPlayed, mLblPlayCount;
RatingComponent mRating;
DateTimeComponent mReleaseDate;
TextComponent mDeveloper;
TextComponent mPublisher;
TextComponent mGenre;
TextComponent mPlayers;
DateTimeComponent mLastPlayed;
TextComponent mPlayCount;
std::vector<TextComponent*> getMDLabels();
std::vector<GuiComponent*> getMDValues();
ScrollableContainer mDescContainer;
TextComponent mDescription;
bool mVideoPlaying;
};

View file

@ -42,6 +42,7 @@ set(CORE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.h
# Guis # Guis
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.h ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.h
@ -96,6 +97,7 @@ set(CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextEditComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/VideoComponent.cpp
# Guis # Guis
${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guis/GuiDetectDevice.cpp

View file

@ -345,3 +345,17 @@ bool GuiComponent::isProcessing() const
{ {
return mIsProcessing; return mIsProcessing;
} }
void GuiComponent::onShow()
{
for(unsigned int i = 0; i < getChildCount(); i++)
getChild(i)->onShow();
}
void GuiComponent::onHide()
{
for(unsigned int i = 0; i < getChildCount(); i++)
getChild(i)->onHide();
}

View file

@ -77,6 +77,9 @@ public:
virtual void onFocusGained() {}; virtual void onFocusGained() {};
virtual void onFocusLost() {}; virtual void onFocusLost() {};
virtual void onShow();
virtual void onHide();
// Default implementation just handles <pos> and <size> tags as normalized float pairs. // 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. // You probably want to keep this behavior for any derived classes as well as add your own.

View file

@ -82,7 +82,15 @@ std::map< std::string, ElementMapType > ThemeData::sElementMap = boost::assign::
("textColor", COLOR) ("textColor", COLOR)
("iconColor", COLOR) ("iconColor", COLOR)
("fontPath", PATH) ("fontPath", PATH)
("fontSize", FLOAT))); ("fontSize", FLOAT)))
("video", makeMap(boost::assign::map_list_of
("pos", NORMALIZED_PAIR)
("size", NORMALIZED_PAIR)
("origin", NORMALIZED_PAIR)
("default", PATH)
("delay", FLOAT)
("showSnapshotNoVideo", BOOLEAN)
("showSnapshotDelay", BOOLEAN)));
namespace fs = boost::filesystem; namespace fs = boost::filesystem;

View file

@ -37,6 +37,7 @@ namespace ThemeFlags
TEXT = 512, TEXT = 512,
FORCE_UPPERCASE = 1024, FORCE_UPPERCASE = 1024,
LINE_SPACING = 2048, LINE_SPACING = 2048,
DELAY = 4096,
ALL = 0xFFFFFFFF ALL = 0xFFFFFFFF
}; };

View file

@ -90,6 +90,11 @@ bool Window::init(unsigned int width, unsigned int height)
void Window::deinit() void Window::deinit()
{ {
// Hide all GUI elements on uninitialisation - this disable
for(auto i = mGuiStack.begin(); i != mGuiStack.end(); i++)
{
(*i)->onHide();
}
InputManager::getInstance()->deinit(); InputManager::getInstance()->deinit();
ResourceManager::getInstance()->unloadAll(); ResourceManager::getInstance()->unloadAll();
Renderer::deinit(); Renderer::deinit();

View file

@ -0,0 +1,518 @@
#include "components/VideoComponent.h"
#include "Renderer.h"
#include "ThemeData.h"
#include "Util.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)
{
memset(&mContext, 0, sizeof(mContext));
// Setup the default configuration
mConfig.showSnapshotDelay = false;
mConfig.showSnapshotNoVideo = false;
mConfig.startDelay = 0;
// Get an empty texture for rendering the video
mTexture = TextureResource::get("");
// Make sure VLC has been initialised
setupVLC();
}
VideoComponent::~VideoComponent()
{
// Stop any currently running video
stopVideo();
}
void VideoComponent::setOrigin(float originX, float originY)
{
mOrigin << originX, originY;
// Update the embeded static image
mStaticImage.setOrigin(originX, originY);
}
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::onSizeChanged()
{
// Update the embeded static image
mStaticImage.onSizeChanged();
}
bool VideoComponent::setVideo(std::string path)
{
// Convert the path into a format VLC can understand
boost::filesystem::path fullPath = getCanonicalPath(path);
fullPath.make_preferred().native();
// 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.generic_string()))
{
// Return true to show that we are going to attempt to play a video
return true;
}
// Return false to show that no video will be displayed
return false;
}
void VideoComponent::setImage(std::string path)
{
// Check that the image has changed
if (path == mStaticImagePath)
return;
mStaticImage.setImage(path);
// Make the image stretch to fill the video region
mStaticImage.setSize(getSize());
mFadeIn = 0.0f;
mStaticImagePath = path;
}
void VideoComponent::setDefaultVideo()
{
setVideo(mConfig.defaultVideoPath);
}
void VideoComponent::setOpacity(unsigned char opacity)
{
mOpacity = opacity;
// Update the embeded static image
mStaticImage.setOpacity(opacity);
}
void VideoComponent::render(const Eigen::Affine3f& parentTrans)
{
float x, y;
Eigen::Affine3f trans = parentTrans * getTransform();
GuiComponent::renderChildren(trans);
Renderer::setMatrix(trans);
// Handle the case where the video is delayed
handleStartDelay();
// 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
{
// This is the case where the video is not currently being displayed. Work out
// if we need to display a static image
if ((mConfig.showSnapshotNoVideo && mVideoPath.empty()) || (mStartDelayed && mConfig.showSnapshotDelay))
{
// Display the static image instead
mStaticImage.setOpacity((unsigned char)(mFadeIn * 255.0f));
mStaticImage.render(parentTrans);
}
}
}
void VideoComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties)
{
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "video");
if(!elem)
{
return;
}
Eigen::Vector2f scale = getParent() ? getParent()->getSize() : Eigen::Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
if ((properties & POSITION) && elem->has("pos"))
{
Eigen::Vector2f denormalized = elem->get<Eigen::Vector2f>("pos").cwiseProduct(scale);
setPosition(Eigen::Vector3f(denormalized.x(), denormalized.y(), 0));
}
if ((properties & ThemeFlags::SIZE) && elem->has("size"))
{
setSize(elem->get<Eigen::Vector2f>("size").cwiseProduct(scale));
}
// position + size also implies origin
if (((properties & ORIGIN) || ((properties & POSITION) && (properties & ThemeFlags::SIZE))) && elem->has("origin"))
setOrigin(elem->get<Eigen::Vector2f>("origin"));
if(elem->has("default"))
mConfig.defaultVideoPath = elem->get<std::string>("default");
if((properties & ThemeFlags::DELAY) && elem->has("delay"))
mConfig.startDelay = (unsigned)(elem->get<float>("delay") * 1000.0f);
if (elem->has("showSnapshotNoVideo"))
mConfig.showSnapshotNoVideo = elem->get<bool>("showSnapshotNoVideo");
if (elem->has("showSnapshotDelay"))
mConfig.showSnapshotDelay = elem->get<bool>("showSnapshotDelay");
// Update the embeded static image
mStaticImage.setPosition(getPosition());
mStaticImage.setMaxSize(getSize());
mStaticImage.setSize(getSize());
}
std::vector<HelpPrompt> VideoComponent::getHelpPrompts()
{
std::vector<HelpPrompt> ret;
ret.push_back(HelpPrompt("a", "select"));
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;
}
}
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
if (mStartDelayed)
{
if (mStartTime > SDL_GetTicks())
{
// Timeout not yet completed
return;
}
// Completed
mStartDelayed = false;
// Clear the playing flag so startVideo works
mIsPlaying = false;
startVideo();
}
}
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()
{
// 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;
mFadeIn = 0.0f;
mStartTime = SDL_GetTicks() + mConfig.startDelay;
}
mIsPlaying = true;
}
}
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();
// If the video start is delayed and there is less than the fade time then set the image fade
// accordingly
if (mStartDelayed)
{
Uint32 ticks = SDL_GetTicks();
if (mStartTime > ticks)
{
Uint32 diff = mStartTime - ticks;
if (diff < FADE_TIME_MS)
{
mFadeIn = (float)diff / (float)FADE_TIME_MS;
return;
}
}
}
// If the fade in is less than 1 then increment it
if (mFadeIn < 1.0f)
{
mFadeIn += deltaTime / (float)FADE_TIME_MS;
if (mFadeIn > 1.0f)
mFadeIn = 1.0f;
}
GuiComponent::update(deltaTime);
}
void VideoComponent::manageState()
{
// We will only show if the component is on display
bool show = mShowing;
// 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();
}
}
}
// 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();
}
}
}
void VideoComponent::onShow()
{
mShowing = true;
manageState();
}
void VideoComponent::onHide()
{
mShowing = false;
manageState();
}

View file

@ -0,0 +1,112 @@
#ifndef _VIDEOCOMPONENT_H_
#define _VIDEOCOMPONENT_H_
#include "platform.h"
#include GLHEADER
#include "GuiComponent.h"
#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
struct Configuration
{
unsigned startDelay;
bool showSnapshotNoVideo;
bool showSnapshotDelay;
std::string defaultVideoPath;
};
public:
static void setupVLC();
VideoComponent(Window* window);
virtual ~VideoComponent();
// Loads the video at the given filepath
bool setVideo(std::string path);
// Loads a static image that is displayed if the video cannot be played
void setImage(std::string path);
// Configures the component to show the default video
void setDefaultVideo();
virtual void onShow() override;
virtual void onHide() 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);
inline void setOrigin(Eigen::Vector2f origin) { setOrigin(origin.x(), origin.y()); }
void onSizeChanged() override;
void setOpacity(unsigned char opacity) override;
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;
virtual std::vector<HelpPrompt> getHelpPrompts() override;
// Returns the center point of the video (takes origin into account).
Eigen::Vector2f getCenter() const;
virtual void update(int deltaTime);
private:
// Start the video Immediately
void startVideo();
// 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;
unsigned mVideoWidth;
unsigned mVideoHeight;
Eigen::Vector2f mOrigin;
std::shared_ptr<TextureResource> mTexture;
float mFadeIn;
std::string mStaticImagePath;
ImageComponent mStaticImage;
boost::filesystem::path mVideoPath;
boost::filesystem::path mPlayingVideoPath;
bool mStartDelayed;
unsigned mStartTime;
bool mIsPlaying;
bool mShowing;
Configuration mConfig;
};
#endif