summaryrefslogtreecommitdiff
path: root/src/player/Player.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/player/Player.cpp')
-rw-r--r--src/player/Player.cpp1835
1 files changed, 1835 insertions, 0 deletions
diff --git a/src/player/Player.cpp b/src/player/Player.cpp
new file mode 100644
index 0000000..77f27d0
--- /dev/null
+++ b/src/player/Player.cpp
@@ -0,0 +1,1835 @@
+//
+// libavg - Media Playback Engine.
+// Copyright (C) 2003-2014 Ulrich von Zadow
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// Current versions can be found at www.libavg.de
+//
+
+#include "Player.h"
+
+#include "../avgconfigwrapper.h"
+
+#include "AVGNode.h"
+#include "DivNode.h"
+#include "WordsNode.h"
+#include "VideoNode.h"
+#include "CameraNode.h"
+#include "ImageNode.h"
+#include "SoundNode.h"
+#include "LineNode.h"
+#include "RectNode.h"
+#include "CurveNode.h"
+#include "PolyLineNode.h"
+#include "PolygonNode.h"
+#include "CircleNode.h"
+#include "MeshNode.h"
+#include "FontStyle.h"
+#include "PluginManager.h"
+#include "TextEngine.h"
+#include "TestHelper.h"
+#include "MainCanvas.h"
+#include "OffscreenCanvas.h"
+#include "TrackerInputDevice.h"
+#include "SDLDisplayEngine.h"
+#include "MultitouchInputDevice.h"
+#include "TUIOInputDevice.h"
+#include "OGLSurface.h"
+#ifdef __APPLE__
+ #include "AppleTrackpadInputDevice.h"
+#endif
+#if defined(_WIN32) && defined(SM_DIGITIZER)
+ #include "Win7TouchInputDevice.h"
+#endif
+#ifdef AVG_ENABLE_MTDEV
+ #include "LibMTDevInputDevice.h"
+#endif
+#if defined(HAVE_XI2_1) || defined(HAVE_XI2_2)
+ #include "XInputMTInputDevice.h"
+#endif
+#include "Contact.h"
+#include "KeyEvent.h"
+#include "MouseEvent.h"
+#include "EventDispatcher.h"
+#include "PublisherDefinition.h"
+#include "BitmapManager.h"
+
+#include "../base/FileHelper.h"
+#include "../base/StringHelper.h"
+#include "../base/OSHelper.h"
+#include "../base/Exception.h"
+#include "../base/Logger.h"
+#include "../base/ConfigMgr.h"
+#include "../base/XMLHelper.h"
+#include "../base/ScopeTimer.h"
+#include "../base/WorkerThread.h"
+#include "../base/DAG.h"
+
+#include "../graphics/BitmapLoader.h"
+#include "../graphics/ShaderRegistry.h"
+#include "../graphics/Display.h"
+
+#include "../imaging/Camera.h"
+
+#include "../audio/AudioEngine.h"
+
+#include <libxml/xmlmemory.h>
+
+#ifdef _WIN32
+#include <direct.h>
+#define getcwd _getcwd
+#endif
+
+#include <iostream>
+
+#ifdef __linux
+#include <fenv.h>
+#endif
+
+#include <glib-object.h>
+#include <typeinfo>
+
+using namespace std;
+using namespace boost;
+
+namespace avg {
+
+Player * Player::s_pPlayer=0;
+
+Player::Player()
+ : Publisher("Player"),
+ m_pDisplayEngine(),
+ m_bDisplayEngineBroken(false),
+ m_bIsTraversingTree(false),
+ m_pMultitouchInputDevice(),
+ m_bInHandleTimers(false),
+ m_bCurrentTimeoutDeleted(false),
+ m_bKeepWindowOpen(false),
+ m_bStopOnEscape(true),
+ m_bIsPlaying(false),
+ m_bFakeFPS(false),
+ m_FakeFPS(0),
+ m_FrameTime(0),
+ m_Volume(1),
+ m_bPythonAvailable(true),
+ m_pLastMouseEvent(new MouseEvent(Event::CURSOR_MOTION, false, false, false,
+ IntPoint(-1, -1), MouseEvent::NO_BUTTON, glm::vec2(-1, -1), 0)),
+ m_EventHookPyFunc(Py_None),
+ m_bMouseEnabled(true)
+{
+ string sDummy;
+#ifdef _WIN32
+ if (getEnv("AVG_WIN_CRASH_SILENTLY", sDummy)) {
+ DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX);
+ SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX);
+ }
+#endif
+#ifdef __linux
+// Turning this on causes fp exceptions in the linux nvidia drivers.
+// feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
+#endif
+ setAffinityMask(true);
+
+ if (s_pPlayer) {
+ throw Exception(AVG_ERR_UNKNOWN, "Player has already been instantiated.");
+ }
+ ThreadProfiler* pProfiler = ThreadProfiler::get();
+ pProfiler->setName("main");
+
+ SDLDisplayEngine::initSDL();
+ initConfig();
+
+ FontStyle::registerType();
+ Node::registerType();
+ AreaNode::registerType();
+ RasterNode::registerType();
+ VectorNode::registerType();
+ FilledVectorNode::registerType();
+
+ DivNode::registerType();
+ CanvasNode::registerType();
+ OffscreenCanvasNode::registerType();
+ AVGNode::registerType();
+ ImageNode::registerType();
+ WordsNode::registerType();
+ VideoNode::registerType();
+ CameraNode::registerType();
+ SoundNode::registerType();
+ LineNode::registerType();
+ RectNode::registerType();
+ CurveNode::registerType();
+ PolyLineNode::registerType();
+ PolygonNode::registerType();
+ CircleNode::registerType();
+ MeshNode::registerType();
+
+ Contact::registerType();
+
+ m_pTestHelper = TestHelperPtr(new TestHelper());
+
+ s_pPlayer = this;
+
+ m_CurDirName = getCWD();
+ if (getEnv("AVG_BREAK_ON_IMPORT", sDummy)) {
+ debugBreak();
+ }
+}
+
+void deletePlayer()
+{
+ delete Player::s_pPlayer;
+ Player::s_pPlayer = 0;
+}
+
+Player::~Player()
+{
+ m_pMainCanvas = MainCanvasPtr();
+ if (m_pDisplayEngine) {
+ m_pDisplayEngine->teardown();
+ }
+ SDLDisplayEngine::quitSDL();
+}
+
+Player* Player::get()
+{
+ if (!s_pPlayer) {
+ s_pPlayer = new Player();
+ atexit(deletePlayer);
+ }
+ return s_pPlayer;
+}
+
+bool Player::exists()
+{
+ return s_pPlayer != 0;
+}
+
+void Player::setResolution(bool bFullscreen, int width, int height, int bpp)
+{
+ errorIfPlaying("Player.setResolution");
+ m_DP.m_bFullscreen = bFullscreen;
+ if (bpp) {
+ m_DP.m_BPP = bpp;
+ }
+ if (width) {
+ m_DP.m_WindowSize.x = width;
+ }
+ if (height) {
+ m_DP.m_WindowSize.y = height;
+ }
+}
+
+bool Player::isFullscreen()
+{
+ return m_DP.m_bFullscreen;
+}
+
+void Player::setWindowFrame(bool bHasWindowFrame)
+{
+ errorIfPlaying("Player.setWindowFrame");
+ m_DP.m_bHasWindowFrame = bHasWindowFrame;
+}
+
+void Player::setWindowPos(int x, int y)
+{
+ errorIfPlaying("Player.setWindowPos");
+ m_DP.m_Pos.x = x;
+ m_DP.m_Pos.y = y;
+}
+
+void Player::setWindowTitle(const string& sTitle)
+{
+ m_pDisplayEngine->setWindowTitle(sTitle);
+}
+
+void Player::useGLES(bool bGLES)
+{
+ errorIfPlaying("Player.useGLES");
+ m_GLConfig.m_bGLES = bGLES;
+#ifdef AVG_ENABLE_EGL
+ m_GLConfig.m_bGLES = true;
+#endif
+ BitmapLoader::init(!m_GLConfig.m_bGLES);
+}
+
+void Player::setOGLOptions(bool bUsePOTTextures, bool bUsePixelBuffers,
+ int multiSampleSamples, GLConfig::ShaderUsage shaderUsage,
+ bool bUseDebugContext)
+{
+ errorIfPlaying("Player.setOGLOptions");
+ m_GLConfig.m_bUsePOTTextures = bUsePOTTextures;
+ m_GLConfig.m_bUsePixelBuffers = bUsePixelBuffers;
+ setMultiSampleSamples(multiSampleSamples);
+ m_GLConfig.m_ShaderUsage = shaderUsage;
+ m_GLConfig.m_bUseDebugContext = bUseDebugContext;
+}
+
+void Player::setMultiSampleSamples(int multiSampleSamples)
+{
+ errorIfPlaying("Player.setMultiSampleSamples");
+ if (multiSampleSamples < 1) {
+ throw Exception(AVG_ERR_OUT_OF_RANGE,
+ "MultiSampleSamples must be 1 or greater (was " +
+ toString(multiSampleSamples) + ").");
+ }
+ m_GLConfig.m_MultiSampleSamples = multiSampleSamples;
+}
+
+void Player::setAudioOptions(int samplerate, int channels)
+{
+ errorIfPlaying("Player.setAudioOptions");
+ m_AP.m_SampleRate = samplerate;
+ m_AP.m_Channels = channels;
+}
+
+void Player::enableGLErrorChecks(bool bEnable)
+{
+ GLContext::enableErrorChecks(bEnable);
+}
+
+glm::vec2 Player::getScreenResolution()
+{
+ return glm::vec2(Display::get()->getScreenResolution());
+}
+
+float Player::getPixelsPerMM()
+{
+ return Display::get()->getPixelsPerMM();
+}
+
+glm::vec2 Player::getPhysicalScreenDimensions()
+{
+ return Display::get()->getPhysicalScreenDimensions();
+}
+
+void Player::assumePixelsPerMM(float ppmm)
+{
+ Display::get()->assumePixelsPerMM(ppmm);
+}
+
+CanvasPtr Player::loadFile(const string& sFilename)
+{
+ errorIfPlaying("Player.loadFile");
+ NodePtr pNode = loadMainNodeFromFile(sFilename);
+ if (m_pMainCanvas) {
+ cleanup(false);
+ }
+
+ initMainCanvas(pNode);
+
+ return m_pMainCanvas;
+}
+
+CanvasPtr Player::loadString(const string& sAVG)
+{
+ errorIfPlaying("Player.loadString");
+ if (m_pMainCanvas) {
+ cleanup(false);
+ }
+
+ NodePtr pNode = loadMainNodeFromString(sAVG);
+ initMainCanvas(pNode);
+
+ return m_pMainCanvas;
+}
+
+OffscreenCanvasPtr Player::loadCanvasFile(const string& sFilename)
+{
+ NodePtr pNode = loadMainNodeFromFile(sFilename);
+ return registerOffscreenCanvas(pNode);
+}
+
+OffscreenCanvasPtr Player::loadCanvasString(const string& sAVG)
+{
+ NodePtr pNode = loadMainNodeFromString(sAVG);
+ return registerOffscreenCanvas(pNode);
+}
+
+CanvasPtr Player::createMainCanvas(const py::dict& params)
+{
+ errorIfPlaying("Player.createMainCanvas");
+ if (m_pMainCanvas) {
+ cleanup(false);
+ }
+
+ NodePtr pNode = createNode("avg", params);
+ initMainCanvas(pNode);
+
+ return m_pMainCanvas;
+}
+
+OffscreenCanvasPtr Player::createCanvas(const py::dict& params)
+{
+ NodePtr pNode = createNode("canvas", params);
+ return registerOffscreenCanvas(pNode);
+}
+
+void Player::deleteCanvas(const string& sID)
+{
+ vector<OffscreenCanvasPtr>::iterator it;
+ for (it = m_pCanvases.begin(); it != m_pCanvases.end(); ++it) {
+ if ((*it)->getID() == sID) {
+ if ((*it)->getNumDependentCanvases() > 0) {
+ throw (Exception(AVG_ERR_INVALID_ARGS,
+ string("deleteCanvas: Canvas with id ")+sID
+ +" is still referenced."));
+ }
+ (*it)->stopPlayback(false);
+ m_pCanvases.erase(it);
+ return;
+ }
+ }
+ throw(Exception(AVG_ERR_OUT_OF_RANGE,
+ string("deleteCanvas: Canvas with id ")+sID+" does not exist."));
+}
+
+CanvasPtr Player::getMainCanvas() const
+{
+ return m_pMainCanvas;
+}
+
+OffscreenCanvasPtr Player::getCanvas(const string& sID) const
+{
+ OffscreenCanvasPtr pCanvas = findCanvas(sID);
+ if (pCanvas) {
+ return pCanvas;
+ } else {
+ throw (Exception(AVG_ERR_INVALID_ARGS,
+ string("Player::getCanvas(): No canvas with id '")+sID+"' exists."));
+ }
+}
+
+void Player::newCanvasDependency()
+{
+ DAG dag;
+ for (unsigned i = 0; i < m_pCanvases.size(); ++i) {
+ set<long> dependentCanvasSet;
+ OffscreenCanvasPtr pCanvas = m_pCanvases[i];
+ const vector<CanvasPtr>& pDependents = pCanvas->getDependentCanvases();
+ for (unsigned j = 0; j < pDependents.size(); ++j) {
+ dependentCanvasSet.insert(pDependents[j]->getHash());
+ }
+ dag.addNode(pCanvas->getHash(), dependentCanvasSet);
+ }
+ dag.addNode(m_pMainCanvas->getHash(), set<long>());
+
+ vector<long> sortedCanvasIDs;
+ try {
+ dag.sort(sortedCanvasIDs);
+ } catch (Exception&) {
+ throw Exception(AVG_ERR_INVALID_ARGS, "Circular dependency between canvases.");
+ }
+
+ vector<OffscreenCanvasPtr> pTempCanvases = m_pCanvases;
+ m_pCanvases.clear();
+ for (unsigned i = 0; i < sortedCanvasIDs.size(); ++i) {
+ long canvasID = sortedCanvasIDs[i];
+ for (unsigned j = 0; j < pTempCanvases.size(); ++j) {
+ OffscreenCanvasPtr pCandidateCanvas = pTempCanvases[j];
+ if (pCandidateCanvas->getHash() == canvasID) {
+ m_pCanvases.push_back(pCandidateCanvas);
+ break;
+ }
+ }
+ }
+}
+
+NodePtr Player::loadMainNodeFromFile(const string& sFilename)
+{
+ string sRealFilename;
+ AVG_TRACE(Logger::category::MEMORY, Logger::severity::INFO,
+ "Player::loadFile(" << sFilename << ")");
+
+ // When loading an avg file, assets are loaded from a directory relative
+ // to the file.
+ char szBuf[1024];
+ char * pBuf = getcwd(szBuf, 1024);
+ if (sFilename[0] == '/') {
+ sRealFilename = sFilename;
+ } else {
+ m_CurDirName = string(pBuf)+"/";
+ sRealFilename = m_CurDirName+sFilename;
+ }
+ m_CurDirName = sRealFilename.substr(0, sRealFilename.rfind('/')+1);
+
+ string sAVG;
+ readWholeFile(sRealFilename, sAVG);
+ NodePtr pNode = internalLoad(sAVG, sRealFilename);
+
+ // Reset the directory to load assets from to the current dir.
+ m_CurDirName = string(pBuf)+"/";
+ return pNode;
+}
+
+NodePtr Player::loadMainNodeFromString(const string& sAVG)
+{
+ AVG_TRACE(Logger::category::MEMORY, Logger::severity::INFO, "Player::loadString()");
+
+ string sEffectiveDoc = removeStartEndSpaces(sAVG);
+ NodePtr pNode = internalLoad(sEffectiveDoc, "");
+ return pNode;
+}
+
+void Player::play()
+{
+ try {
+ if (!m_pMainCanvas) {
+ throw Exception(AVG_ERR_NO_NODE, "Play called, but no xml file loaded.");
+ }
+ initPlayback();
+ notifySubscribers("PLAYBACK_START");
+ try {
+ ThreadProfiler::get()->start();
+ doFrame(true);
+ while (!m_bStopping) {
+ doFrame(false);
+ }
+ notifySubscribers("PLAYBACK_END");
+ } catch (...) {
+ cleanup(true);
+ m_bDisplayEngineBroken = true;
+ throw;
+ }
+ cleanup(false);
+ AVG_TRACE(Logger::category::PLAYER, Logger::severity::INFO, "Playback ended.");
+ } catch (Exception& ex) {
+ m_bIsPlaying = false;
+ AVG_LOG_ERROR(ex.getStr());
+ throw;
+ }
+}
+
+void Player::stop()
+{
+ if (m_bIsPlaying) {
+ m_bStopping = true;
+ } else {
+ cleanup(false);
+ }
+}
+
+bool Player::isStopping()
+{
+ return m_bStopping;
+}
+
+void Player::initPlayback(const std::string& sShaderPath)
+{
+ m_bIsPlaying = true;
+ AVG_TRACE(Logger::category::PLAYER, Logger::severity::INFO, "Playback started.");
+ initGraphics(sShaderPath);
+ initAudio();
+ try {
+ for (unsigned i = 0; i < m_pCanvases.size(); ++i) {
+ m_pCanvases[i]->initPlayback();
+ }
+ m_pMainCanvas->initPlayback(m_pDisplayEngine);
+ } catch (Exception&) {
+ cleanup(true);
+ m_bDisplayEngineBroken = true;
+ throw;
+ }
+ m_pEventDispatcher->addInputDevice(
+ boost::dynamic_pointer_cast<IInputDevice>(m_pDisplayEngine));
+ m_pEventDispatcher->addInputDevice(m_pTestHelper);
+
+ m_pDisplayEngine->initRender();
+ Display::get()->rereadScreenResolution();
+ m_bStopping = false;
+ if (m_pMultitouchInputDevice) {
+ m_pMultitouchInputDevice->start();
+ }
+
+ m_FrameTime = 0;
+ m_NumFrames = 0;
+}
+
+bool Player::isPlaying()
+{
+ return m_bIsPlaying;
+}
+
+void Player::setFramerate(float rate)
+{
+ if (m_bIsPlaying) {
+ m_pDisplayEngine->setFramerate(rate);
+ }
+ m_DP.m_Framerate = rate;
+ m_DP.m_VBRate = 0;
+}
+
+void Player::setVBlankFramerate(int rate)
+{
+ if (m_bIsPlaying) {
+ m_pDisplayEngine->setVBlankRate(rate);
+ }
+ m_DP.m_Framerate = 0;
+ m_DP.m_VBRate = rate;
+}
+
+float Player::getEffectiveFramerate()
+{
+ if (m_bIsPlaying) {
+ if (m_bFakeFPS) {
+ return m_FakeFPS;
+ } else {
+ return m_pDisplayEngine->getEffectiveFramerate();
+ }
+ } else {
+ return 0;
+ }
+}
+
+TestHelper * Player::getTestHelper()
+{
+ return m_pTestHelper.get();
+}
+
+void Player::setFakeFPS(float fps)
+{
+ if (fabs(fps + 1.0) < 0.0001) {
+ // fps = -1
+ m_bFakeFPS = false;
+ } else {
+ m_bFakeFPS = true;
+ m_FakeFPS = fps;
+ }
+
+ if (AudioEngine::get()) {
+ AudioEngine::get()->setAudioEnabled(!m_bFakeFPS);
+ }
+}
+
+void Player::addInputDevice(IInputDevicePtr pSource)
+{
+ if (!m_pEventDispatcher) {
+ throw Exception(AVG_ERR_UNSUPPORTED,
+ "You must use loadFile() before addInputDevice().");
+ }
+ m_pEventDispatcher->addInputDevice(pSource);
+}
+
+long long Player::getFrameTime()
+{
+ return m_FrameTime;
+}
+
+float Player::getFrameDuration()
+{
+ if (!m_bIsPlaying) {
+ throw Exception(AVG_ERR_UNSUPPORTED,
+ "Must call Player.play() before getFrameDuration().");
+ }
+ if (m_bFakeFPS) {
+ return 1000.0f/m_FakeFPS;
+ } else {
+ float framerate = m_pDisplayEngine->getEffectiveFramerate();
+ if (framerate > 0) {
+ return 1000.f/framerate;
+ } else {
+ return 0;
+ }
+ }
+}
+
+TrackerInputDevice * Player::getTracker()
+{
+ TrackerInputDevice* pTracker = dynamic_cast<TrackerInputDevice*>(
+ m_pMultitouchInputDevice.get());
+ return pTracker;
+}
+
+void Player::enableMultitouch()
+{
+ if (!m_bIsPlaying) {
+ throw Exception(AVG_ERR_UNSUPPORTED,
+ "Must call Player.play() before enableMultitouch().");
+ }
+
+ string sDriver;
+ getEnv("AVG_MULTITOUCH_DRIVER", sDriver);
+ if (sDriver == "") {
+#if defined(_WIN32) && defined(SM_DIGITIZER)
+ sDriver = "WIN7TOUCH";
+#elif defined(HAVE_XI2_1) || defined(HAVE_XI2_2)
+ sDriver = "XINPUT";
+#elif defined (AVG_ENABLE_MTDEV)
+ sDriver = "LINUXMTDEV";
+#else
+ AVG_LOG_WARNING("Valid values for AVG_MULTITOUCH_DRIVER are WIN7TOUCH, XINPUT, LINUXMTDEV, TRACKER, TUIO and APPLETRACKPAD.");
+ throw Exception(AVG_ERR_MT_INIT,
+ "Multitouch support: No default driver available. Set AVG_MULTITOUCH_DRIVER.");
+#endif
+ }
+ if (sDriver == "TUIO") {
+ m_pMultitouchInputDevice = IInputDevicePtr(new TUIOInputDevice);
+#if defined(_WIN32) && defined(SM_DIGITIZER)
+ } else if (sDriver == "WIN7TOUCH") {
+ m_pMultitouchInputDevice = IInputDevicePtr(new Win7TouchInputDevice);
+#endif
+ } else if (sDriver == "XINPUT" || sDriver == "XINPUT21") {
+#if defined(HAVE_XI2_1) || defined(HAVE_XI2_2)
+ m_pMultitouchInputDevice = IInputDevicePtr(new XInputMTInputDevice);
+#else
+ throw Exception(AVG_ERR_MT_INIT,
+ "XInput multitouch event source: Support not configured.'");
+#endif
+#ifdef AVG_ENABLE_MTDEV
+ } else if (sDriver == "LINUXMTDEV") {
+ m_pMultitouchInputDevice = IInputDevicePtr(new LibMTDevInputDevice);
+#endif
+#ifdef __APPLE__
+ } else if (sDriver == "APPLETRACKPAD") {
+ m_pMultitouchInputDevice = IInputDevicePtr(new AppleTrackpadInputDevice);
+#endif
+ } else if (sDriver == "TRACKER") {
+ m_pMultitouchInputDevice = IInputDevicePtr(new TrackerInputDevice);
+ } else {
+ AVG_LOG_WARNING("Valid values for AVG_MULTITOUCH_DRIVER are WIN7TOUCH, XINPUT, LINUXMTDEV, TRACKER, TUIO and APPLETRACKPAD.");
+ throw Exception(AVG_ERR_UNSUPPORTED, string("Unsupported multitouch driver '")+
+ sDriver +"'.");
+ }
+ if (m_bIsPlaying) {
+ try {
+ m_pMultitouchInputDevice->start();
+ } catch (Exception&) {
+ m_pMultitouchInputDevice = IInputDevicePtr();
+ throw;
+ }
+ }
+ addInputDevice(m_pMultitouchInputDevice);
+}
+
+void Player::enableMouse(bool enabled)
+{
+ m_bMouseEnabled = enabled;
+
+ if (m_pEventDispatcher) {
+ m_pEventDispatcher->enableMouse(enabled);
+ }
+}
+
+bool Player::isMultitouchAvailable() const
+{
+ if (m_bIsPlaying) {
+ return m_pMultitouchInputDevice != 0;
+ } else {
+ throw Exception(AVG_ERR_UNSUPPORTED,
+ "Must call Player.play() before isMultitouchAvailable().");
+ }
+}
+
+void Player::setEventCapture(NodePtr pNode, int cursorID=MOUSECURSORID)
+{
+ std::map<int, EventCaptureInfoPtr>::iterator it =
+ m_EventCaptureInfoMap.find(cursorID);
+ if (it != m_EventCaptureInfoMap.end()) {
+ EventCaptureInfoPtr pCaptureInfo = it->second;
+ NodePtr pOldNode = pCaptureInfo->m_pNode;
+ if (pOldNode->getState() != Node::NS_UNCONNECTED) {
+ if (pOldNode == pNode) {
+ pCaptureInfo->m_CaptureCount++;
+ } else {
+ throw Exception(AVG_ERR_INVALID_CAPTURE, "setEventCapture called for '"
+ + pNode->getID() + "', but cursor already captured by '"
+ + pOldNode->getID() + "'.");
+ }
+ }
+ } else {
+ m_EventCaptureInfoMap[cursorID] = EventCaptureInfoPtr(
+ new EventCaptureInfo(pNode));
+ }
+}
+
+void Player::releaseEventCapture(int cursorID)
+{
+ std::map<int, EventCaptureInfoPtr>::iterator it =
+ m_EventCaptureInfoMap.find(cursorID);
+ if (it == m_EventCaptureInfoMap.end() ||
+ (it->second->m_pNode->getState() == Node::NS_UNCONNECTED))
+ {
+ throw Exception(AVG_ERR_INVALID_CAPTURE,
+ "releaseEventCapture called, but cursor not captured.");
+ } else {
+ it->second->m_CaptureCount--;
+ if (it->second->m_CaptureCount == 0) {
+ m_EventCaptureInfoMap.erase(cursorID);
+ }
+ }
+}
+
+bool Player::isCaptured(int cursorID)
+{
+ std::map<int, EventCaptureInfoPtr>::iterator it =
+ m_EventCaptureInfoMap.find(cursorID);
+ return (it != m_EventCaptureInfoMap.end());
+}
+
+void Player::removeDeadEventCaptures()
+{
+ std::map<int, EventCaptureInfoPtr>::iterator it;
+ for (it = m_EventCaptureInfoMap.begin(); it != m_EventCaptureInfoMap.end();) {
+ std::map<int, EventCaptureInfoPtr>::iterator lastIt = it;
+ it++;
+ if (lastIt->second->m_pNode->getState() == Node::NS_UNCONNECTED) {
+ m_EventCaptureInfoMap.erase(lastIt);
+ }
+ }
+}
+
+int Player::setInterval(int time, PyObject * pyfunc)
+{
+ return internalSetTimeout(time, pyfunc, true);
+}
+
+int Player::setTimeout(int time, PyObject * pyfunc)
+{
+ return internalSetTimeout(time, pyfunc, false);
+}
+
+int Player::setOnFrameHandler(PyObject * pyfunc)
+{
+ avgDeprecationWarning("1.8", "Player.setOnFrameHandler",
+ "Player.subscribe(Player.ON_FRAME)");
+ return internalSetTimeout(0, pyfunc, true);
+}
+
+bool Player::clearInterval(int id)
+{
+ vector<Timeout*>::iterator it;
+ for (it = m_PendingTimeouts.begin(); it != m_PendingTimeouts.end(); it++) {
+ if (id == (*it)->getID()) {
+ if (it == m_PendingTimeouts.begin() && m_bInHandleTimers) {
+ m_bCurrentTimeoutDeleted = true;
+ }
+ delete *it;
+ m_PendingTimeouts.erase(it);
+ return true;
+ }
+ }
+ for (it = m_NewTimeouts.begin(); it != m_NewTimeouts.end(); it++) {
+ if (id == (*it)->getID()) {
+ delete *it;
+ m_NewTimeouts.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+void Player::callFromThread(PyObject * pyfunc)
+{
+ lock_guard lock(m_AsyncCallMutex);
+ Timeout* pTimeout = new Timeout(0, pyfunc, false, getFrameTime());
+ m_AsyncCalls.push_back(pTimeout);
+}
+
+MouseEventPtr Player::getMouseState() const
+{
+ return m_pLastMouseEvent;
+}
+
+EventPtr Player::getCurrentEvent() const
+{
+ if (!m_pCurrentEvent) {
+ throw Exception(AVG_ERR_UNSUPPORTED, "No current event.");
+ }
+ return m_pCurrentEvent;
+}
+
+void Player::setMousePos(const IntPoint& pos)
+{
+ m_pDisplayEngine->setMousePos(pos);
+}
+
+int Player::getKeyModifierState() const
+{
+ return m_pDisplayEngine->getKeyModifierState();
+}
+
+BitmapPtr Player::screenshot()
+{
+ if (!m_bIsPlaying) {
+ throw Exception(AVG_ERR_UNSUPPORTED,
+ "Must call Player.play() before screenshot().");
+ }
+ if (GLContext::getMain()->isGLES()) {
+ // Some GLES implementations invalidate the buffer after eglSwapBuffers.
+ // The only way we can get at the contents at this point is to rerender them.
+ m_pMainCanvas->render(m_pDisplayEngine->getWindowSize(), false);
+ }
+ return m_pDisplayEngine->screenshot();
+}
+
+void Player::showCursor(bool bShow)
+{
+ if (m_pDisplayEngine) {
+ m_pDisplayEngine->showCursor(bShow);
+ }
+ m_DP.m_bShowCursor = bShow;
+}
+
+bool Player::isCursorShown()
+{
+ return m_DP.m_bShowCursor;
+}
+
+void Player::setCursor(const Bitmap* pBmp, IntPoint hotSpot)
+{
+ IntPoint size = pBmp->getSize();
+ if (size.x % 8 != 0 || size.y % 8 != 0 || pBmp->getPixelFormat() != R8G8B8A8) {
+ throw Exception(AVG_ERR_INVALID_ARGS,
+ "setCursor: Bitmap size must be divisible by 8 and in RGBA format.");
+ }
+ int i = -1;
+ unsigned char * pData = new unsigned char[size.x*size.y/8];
+ unsigned char * pMask = new unsigned char[size.x*size.y/8];
+ Pixel32 * pLine = (Pixel32*)(pBmp->getPixels());
+ int stride = pBmp->getStride()/4;
+ for (int y = 0; y < size.y; ++y) {
+ Pixel32 * pPixel = pLine;
+ for (int x = 0; x < size.x; ++x) {
+ if (x % 8 == 0) {
+ i++;
+ pData[i] = 0;
+ pMask[i] = 0;
+ } else {
+ pData[i] <<= 1;
+ pMask[i] <<= 1;
+ }
+ if (pPixel->getA() > 127) {
+ pMask[i] |= 0x01;
+ if (pPixel->getR() < 128) {
+ // Black Pixel
+ pData[i] |= 0x01;
+ }
+ }
+ pPixel++;
+ }
+ pLine += stride;
+ }
+ SDL_Cursor * pCursor = SDL_CreateCursor(pData, pMask, size.x, size.y,
+ hotSpot.x, hotSpot.y);
+ SDL_SetCursor(pCursor);
+ delete[] pData;
+ delete[] pMask;
+}
+
+NodePtr Player::getElementByID(const std::string& sID)
+{
+ if (m_pMainCanvas) {
+ return m_pMainCanvas->getElementByID(sID);
+ } else {
+ return NodePtr();
+ }
+}
+
+AVGNodePtr Player::getRootNode()
+{
+ if (m_pMainCanvas) {
+ return dynamic_pointer_cast<AVGNode>(m_pMainCanvas->getRootNode());
+ } else {
+ return AVGNodePtr();
+ }
+}
+
+string Player::getCurDirName()
+{
+ return m_CurDirName;
+}
+
+std::string Player::getRootMediaDir()
+{
+ string sMediaDir;
+ if (m_pMainCanvas) {
+ sMediaDir = m_pMainCanvas->getRootNode()->getEffectiveMediaDir();
+ } else {
+ sMediaDir = m_CurDirName;
+ }
+ return sMediaDir;
+}
+
+void Player::disablePython()
+{
+ m_bPythonAvailable = false;
+}
+
+void Player::startTraversingTree()
+{
+ AVG_ASSERT(!m_bIsTraversingTree);
+ m_bIsTraversingTree = true;
+}
+
+void Player::endTraversingTree()
+{
+ AVG_ASSERT(m_bIsTraversingTree);
+ m_bIsTraversingTree = false;
+}
+
+bool Player::isTraversingTree() const
+{
+ return m_bIsTraversingTree;
+}
+
+void Player::registerFrameEndListener(IFrameEndListener* pListener)
+{
+ AVG_ASSERT(m_pMainCanvas);
+ m_pMainCanvas->registerFrameEndListener(pListener);
+}
+
+void Player::unregisterFrameEndListener(IFrameEndListener* pListener)
+{
+ if (m_pMainCanvas) {
+ m_pMainCanvas->unregisterFrameEndListener(pListener);
+ }
+}
+
+void Player::registerPlaybackEndListener(IPlaybackEndListener* pListener)
+{
+ AVG_ASSERT(m_pMainCanvas);
+ m_pMainCanvas->registerPlaybackEndListener(pListener);
+}
+
+void Player::unregisterPlaybackEndListener(IPlaybackEndListener* pListener)
+{
+ if (m_pMainCanvas) {
+ m_pMainCanvas->unregisterPlaybackEndListener(pListener);
+ }
+}
+
+void Player::registerPreRenderListener(IPreRenderListener* pListener)
+{
+ AVG_ASSERT(m_pMainCanvas);
+ m_pMainCanvas->registerPreRenderListener(pListener);
+}
+
+void Player::unregisterPreRenderListener(IPreRenderListener* pListener)
+{
+ if (m_pMainCanvas) {
+ m_pMainCanvas->unregisterPreRenderListener(pListener);
+ }
+}
+
+bool Player::handleEvent(EventPtr pEvent)
+{
+ AVG_ASSERT(pEvent);
+ EventPtr pLastEvent = m_pCurrentEvent;
+ m_pCurrentEvent = pEvent;
+ if (MouseEventPtr pMouseEvent = boost::dynamic_pointer_cast<MouseEvent>(pEvent)) {
+ m_pLastMouseEvent = pMouseEvent;
+ }
+
+ if (CursorEventPtr pCursorEvent = boost::dynamic_pointer_cast<CursorEvent>(pEvent)) {
+ if (pEvent->getType() == Event::CURSOR_OUT ||
+ pEvent->getType() == Event::CURSOR_OVER)
+ {
+ pEvent->trace();
+ pCursorEvent->getNode()->handleEvent(pEvent);
+ } else {
+ handleCursorEvent(pCursorEvent);
+ }
+ }
+ else if (KeyEventPtr pKeyEvent = boost::dynamic_pointer_cast<KeyEvent>(pEvent))
+ {
+ pEvent->trace();
+ switch (pEvent->getType()) {
+ case Event::KEY_DOWN:
+ notifySubscribers("KEY_DOWN", pEvent);
+ break;
+ case Event::KEY_UP:
+ notifySubscribers("KEY_UP", pEvent);
+ break;
+ default:
+ AVG_ASSERT(false);
+ }
+ getRootNode()->handleEvent(pKeyEvent);
+ if (getStopOnEscape() && pEvent->getType() == Event::KEY_DOWN
+ && pKeyEvent->getKeyCode() == avg::key::KEY_ESCAPE)
+ {
+ stop();
+ }
+ }
+ else {
+ if (pEvent->getType() != Event::QUIT) {
+ pEvent->trace();
+ getRootNode()->handleEvent(pEvent);
+ }
+ else {
+ stop();
+ }
+ }
+ m_pCurrentEvent = pLastEvent;
+ return true;
+}
+
+static ProfilingZoneID MainProfilingZone("Player - Total frame time");
+static ProfilingZoneID TimersProfilingZone("Player - handleTimers");
+static ProfilingZoneID EventsProfilingZone("Dispatch events");
+static ProfilingZoneID MainCanvasProfilingZone("Main canvas rendering");
+static ProfilingZoneID OffscreenProfilingZone("Offscreen rendering");
+
+void Player::doFrame(bool bFirstFrame)
+{
+ {
+ ScopeTimer Timer(MainProfilingZone);
+ if (!bFirstFrame) {
+ m_NumFrames++;
+ if (m_bFakeFPS) {
+ m_FrameTime = (long long)((m_NumFrames*1000.0)/m_FakeFPS);
+ } else {
+ m_FrameTime = m_pDisplayEngine->getDisplayTime();
+ }
+ {
+ ScopeTimer Timer(TimersProfilingZone);
+ handleTimers();
+ }
+ {
+ ScopeTimer Timer(EventsProfilingZone);
+ m_pEventDispatcher->dispatch();
+ sendFakeEvents();
+ removeDeadEventCaptures();
+ }
+ }
+ for (unsigned i = 0; i < m_pCanvases.size(); ++i) {
+ ScopeTimer Timer(OffscreenProfilingZone);
+ dispatchOffscreenRendering(m_pCanvases[i].get());
+ }
+ {
+ ScopeTimer Timer(MainCanvasProfilingZone);
+ m_pMainCanvas->doFrame(m_bPythonAvailable);
+ }
+ GLContext::mandatoryCheckError("End of frame");
+ if (m_bPythonAvailable) {
+ Py_BEGIN_ALLOW_THREADS;
+ try {
+ endFrame();
+ } catch(...) {
+ Py_BLOCK_THREADS;
+ throw;
+ }
+ Py_END_ALLOW_THREADS;
+ } else {
+ endFrame();
+ }
+ }
+ ThreadProfiler::get()->reset();
+ if (m_NumFrames == 5) {
+ ThreadProfiler::get()->restart();
+ }
+}
+
+void Player::endFrame()
+{
+ m_pDisplayEngine->frameWait();
+ m_pDisplayEngine->swapBuffers();
+ m_pDisplayEngine->checkJitter();
+}
+
+float Player::getFramerate()
+{
+ if (!m_pDisplayEngine) {
+ return m_DP.m_Framerate;
+ }
+ return m_pDisplayEngine->getFramerate();
+}
+
+float Player::getVideoRefreshRate()
+{
+ return Display::get()->getRefreshRate();
+}
+
+size_t Player::getVideoMemInstalled()
+{
+ if (!m_pDisplayEngine) {
+ throw Exception(AVG_ERR_UNSUPPORTED,
+ "Player.getVideoMemInstalled must be called after Player.play().");
+ }
+ return GLContext::getMain()->getVideoMemInstalled();
+}
+
+size_t Player::getVideoMemUsed()
+{
+ if (!m_pDisplayEngine) {
+ throw Exception(AVG_ERR_UNSUPPORTED,
+ "Player.getVideoMemUsed must be called after Player.play().");
+ }
+ return GLContext::getMain()->getVideoMemUsed();
+}
+
+void Player::setGamma(float red, float green, float blue)
+{
+ if (m_pDisplayEngine) {
+ m_pDisplayEngine->setGamma(red, green, blue);
+ }
+ m_DP.m_Gamma[0] = red;
+ m_DP.m_Gamma[1] = green;
+ m_DP.m_Gamma[2] = blue;
+}
+
+void Player::initConfig()
+{
+ // Get data from config files.
+ ConfigMgr* pMgr = ConfigMgr::get();
+
+ m_DP.m_BPP = atoi(pMgr->getOption("scr", "bpp")->c_str());
+ if (m_DP.m_BPP != 15 && m_DP.m_BPP != 16 && m_DP.m_BPP != 24 && m_DP.m_BPP != 32) {
+ AVG_LOG_ERROR("BPP must be 15, 16, 24 or 32. Current value is "
+ << m_DP.m_BPP << ". Aborting." );
+ exit(-1);
+ }
+ m_DP.m_bFullscreen = pMgr->getBoolOption("scr", "fullscreen", false);
+
+ m_DP.m_WindowSize.x = atoi(pMgr->getOption("scr", "windowwidth")->c_str());
+ m_DP.m_WindowSize.y = atoi(pMgr->getOption("scr", "windowheight")->c_str());
+
+ if (m_DP.m_bFullscreen && (m_DP.m_WindowSize != IntPoint(0, 0))) {
+ AVG_LOG_ERROR("Can't set fullscreen and window size at once. Aborting.");
+ exit(-1);
+ }
+ if (m_DP.m_WindowSize.x != 0 && m_DP.m_WindowSize.y != 0) {
+ AVG_LOG_ERROR("Can't set window width and height at once");
+ AVG_LOG_ERROR("(aspect ratio is determined by avg file). Aborting.");
+ exit(-1);
+ }
+
+ m_AP.m_Channels = atoi(pMgr->getOption("aud", "channels")->c_str());
+ m_AP.m_SampleRate = atoi(pMgr->getOption("aud", "samplerate")->c_str());
+ m_AP.m_OutputBufferSamples =
+ atoi(pMgr->getOption("aud", "outputbuffersamples")->c_str());
+
+ m_GLConfig.m_bGLES = pMgr->getBoolOption("scr", "gles", false);
+ m_GLConfig.m_bUsePOTTextures = pMgr->getBoolOption("scr", "usepow2textures", false);
+
+ m_GLConfig.m_bUsePixelBuffers = pMgr->getBoolOption("scr", "usepixelbuffers", true);
+ int multiSampleSamples = pMgr->getIntOption("scr", "multisamplesamples", 8);
+ if (multiSampleSamples < 1) {
+ AVG_LOG_ERROR("multisamplesamples must be >= 1. Aborting")
+ exit(-1);
+ }
+ m_GLConfig.m_MultiSampleSamples = multiSampleSamples;
+
+ string sShaderUsage;
+ pMgr->getStringOption("scr", "shaderusage", "auto", sShaderUsage);
+ if (sShaderUsage == "full") {
+ m_GLConfig.m_ShaderUsage = GLConfig::FULL;
+ } else if (sShaderUsage == "minimal") {
+ m_GLConfig.m_ShaderUsage = GLConfig::MINIMAL;
+ } else if (sShaderUsage == "auto") {
+ m_GLConfig.m_ShaderUsage = GLConfig::AUTO;
+ } else {
+ throw Exception(AVG_ERR_OUT_OF_RANGE,
+ "avgrc parameter shaderusage must be full, minimal, fragmentonly or auto");
+ }
+ string sDummy;
+ m_GLConfig.m_bUseDebugContext = getEnv("AVG_USE_DEBUG_GL_CONTEXT", sDummy);
+#ifdef AVG_ENABLE_EGL
+ m_GLConfig.m_bGLES = true;
+#endif
+ BitmapLoader::init(!m_GLConfig.m_bGLES);
+
+ pMgr->getGammaOption("scr", "gamma", m_DP.m_Gamma);
+}
+
+void Player::initGraphics(const string& sShaderPath)
+{
+ if (!Display::isInitialized()) {
+ ConfigMgr* pMgr = ConfigMgr::get();
+ float dotsPerMM = float(atof(pMgr->getOption("scr", "dotspermm")->c_str()));
+ Display::get()->assumePixelsPerMM(dotsPerMM);
+ }
+ // Init display configuration.
+ AVG_TRACE(Logger::category::CONFIG, Logger::severity::INFO,
+ "Display bpp: " << m_DP.m_BPP);
+
+ if (m_bDisplayEngineBroken) {
+ m_bDisplayEngineBroken = false;
+ m_pDisplayEngine->teardown();
+ m_pDisplayEngine = SDLDisplayEnginePtr();
+ }
+
+ if (!m_pDisplayEngine) {
+ m_pDisplayEngine = SDLDisplayEnginePtr(new SDLDisplayEngine());
+ }
+ AVG_TRACE(Logger::category::CONFIG, Logger::severity::INFO,
+ "Requested OpenGL configuration: ");
+ m_GLConfig.log();
+ m_DP.m_WindowSize = m_pDisplayEngine->calcWindowSize(m_DP);
+ if (m_pDisplayEngine->getWindowSize() != m_DP.m_WindowSize ||
+ m_pDisplayEngine->isFullscreen() == true)
+ {
+ m_pDisplayEngine->teardown();
+ m_pDisplayEngine->init(m_DP, m_GLConfig);
+ }
+ AVG_TRACE(Logger::category::CONFIG, Logger::severity::INFO,
+ "Pixels per mm: " << Display::get()->getPixelsPerMM());
+ if (sShaderPath != "") {
+ ShaderRegistry::get()->setShaderPath(sShaderPath);
+ }
+ m_pDisplayEngine->setGamma(1.0, 1.0, 1.0);
+ m_GLConfig = GLContext::getCurrent()->getConfig();
+}
+
+void Player::initAudio()
+{
+ AudioEngine* pAudioEngine = AudioEngine::get();
+ if (!pAudioEngine) {
+ pAudioEngine = new AudioEngine();
+ }
+ pAudioEngine->init(m_AP, m_Volume);
+ pAudioEngine->setAudioEnabled(!m_bFakeFPS);
+ pAudioEngine->play();
+}
+
+void Player::initMainCanvas(NodePtr pRootNode)
+{
+ m_pEventDispatcher = EventDispatcherPtr(new EventDispatcher(this, m_bMouseEnabled));
+ m_pMainCanvas = MainCanvasPtr(new MainCanvas(this));
+ m_pMainCanvas->setRoot(pRootNode);
+ m_DP.m_Size = m_pMainCanvas->getSize();
+
+ registerFrameEndListener(BitmapManager::get());
+}
+
+NodePtr Player::internalLoad(const string& sAVG, const string& sFilename)
+{
+ XMLParser parser;
+ parser.setDTD(TypeRegistry::get()->getDTD(), "avg.dtd");
+ parser.parse(sAVG, sFilename);
+ xmlNodePtr xmlNode = parser.getRootNode();
+ NodePtr pNode = createNodeFromXml(parser.getDoc(), xmlNode);
+ if (!pNode) {
+ throw (Exception(AVG_ERR_XML_PARSE,
+ "Root node of an avg tree needs to be an <avg> node."));
+ }
+ return pNode;
+}
+
+SDLDisplayEnginePtr Player::safeGetDisplayEngine()
+{
+ if (!m_pDisplayEngine) {
+ m_pDisplayEngine = SDLDisplayEnginePtr(new SDLDisplayEngine());
+ }
+ return m_pDisplayEngine;
+
+}
+
+NodePtr Player::createNode(const string& sType,
+ const py::dict& params, const boost::python::object& self)
+{
+ DivNodePtr pParentNode;
+ py::dict attrs = params;
+ py::object parent;
+ if (params.has_key("parent")) {
+ parent = params["parent"];
+ attrs.attr("__delitem__")("parent");
+ pParentNode = py::extract<DivNodePtr>(parent);
+ }
+ NodePtr pNode = dynamic_pointer_cast<Node>(
+ TypeRegistry::get()->createObject(sType, attrs));
+
+ // See if the class names of self and pNode match. If they don't, there is a
+ // python derived class that's being constructed and we can't set parent here.
+ string sSelfClassName = py::extract<string>(
+ self.attr("__class__").attr("__name__"));
+ py::object pythonClassName =
+ (py::object(pNode).attr("__class__").attr("__name__"));
+ string sThisClassName = py::extract<string>(pythonClassName);
+ bool bHasDerivedClass = sSelfClassName != sThisClassName &&
+ sSelfClassName != "NoneType";
+ if (bHasDerivedClass) {
+ if (pParentNode) {
+ throw Exception(AVG_ERR_UNSUPPORTED,
+ "Can't pass 'parent' parameter to C++ class constructor if there is a derived python class. Use Node.registerInstance() instead.");
+ }
+ pNode->registerInstance(self.ptr(), pParentNode);
+ } else {
+ pNode->registerInstance(0, pParentNode);
+ }
+ if (parent) {
+ attrs["parent"] = parent;
+ }
+ return pNode;
+}
+
+NodePtr Player::createNodeFromXmlString(const string& sXML)
+{
+ xmlPedanticParserDefault(1);
+ xmlDoValidityCheckingDefaultValue =0;
+
+ XMLParser parser;
+ parser.setDTD(TypeRegistry::get()->getDTD(), "avg.dtd");
+ parser.parse(sXML, "");
+
+// cvp->error = xmlParserValidityError;
+// cvp->warning = xmlParserValidityWarning;
+ xmlNodePtr xmlNode = parser.getRootNode();
+ NodePtr pNode = createNodeFromXml(parser.getDoc(), xmlNode);
+
+ return pNode;
+}
+
+NodePtr Player::createNodeFromXml(const xmlDocPtr xmlDoc,
+ const xmlNodePtr xmlNode)
+{
+ const char * nodeType = (const char *)xmlNode->name;
+
+ if (!strcmp (nodeType, "text") || !strcmp (nodeType, "comment")) {
+ // Ignore whitespace & comments
+ return NodePtr();
+ }
+ NodePtr pCurNode = dynamic_pointer_cast<Node>(
+ TypeRegistry::get()->createObject(nodeType, xmlNode));
+ if (!strcmp(nodeType, "words")) {
+ // TODO: This is an end-run around the generic serialization mechanism
+ // that will probably break at some point.
+ string s = getXmlChildrenAsString(xmlDoc, xmlNode);
+ boost::dynamic_pointer_cast<WordsNode>(pCurNode)->setTextFromNodeValue(s);
+ } else {
+ // If this is a container, recurse into children
+ if (pCurNode->getDefinition()->hasChildren()) {
+ xmlNodePtr curXmlChild = xmlNode->xmlChildrenNode;
+ while (curXmlChild) {
+ NodePtr curChild = createNodeFromXml(xmlDoc, curXmlChild);
+ if (curChild) {
+ DivNodePtr pDivNode = boost::dynamic_pointer_cast<DivNode>(pCurNode);
+ pDivNode->appendChild(curChild);
+ }
+ curXmlChild = curXmlChild->next;
+ }
+ }
+ }
+ return pCurNode;
+}
+
+OffscreenCanvasPtr Player::registerOffscreenCanvas(NodePtr pNode)
+{
+ OffscreenCanvasPtr pCanvas(new OffscreenCanvas(this));
+ pCanvas->setRoot(pNode);
+ if (findCanvas(pCanvas->getID())) {
+ throw (Exception(AVG_ERR_INVALID_ARGS,
+ string("Duplicate canvas id ")+pCanvas->getID()));
+ }
+ m_pCanvases.push_back(pCanvas);
+ if (m_bIsPlaying) {
+ try {
+ pCanvas->initPlayback();
+ } catch (...) {
+ m_pCanvases.pop_back();
+ throw;
+ }
+ }
+ return pCanvas;
+}
+
+OffscreenCanvasPtr Player::findCanvas(const string& sID) const
+{
+ for (unsigned i=0; i<m_pCanvases.size(); ++i) {
+ if (m_pCanvases[i]->getID() == sID) {
+ return m_pCanvases[i];
+ }
+ }
+ return OffscreenCanvasPtr();
+}
+
+void Player::sendFakeEvents()
+{
+ std::map<int, CursorStatePtr>::iterator it;
+ for (it = m_pLastCursorStates.begin(); it != m_pLastCursorStates.end(); ++it) {
+ CursorStatePtr pState = it->second;
+ handleCursorEvent(pState->getLastEvent(), true);
+ }
+}
+
+void Player::sendOver(const CursorEventPtr pOtherEvent, Event::Type type,
+ NodePtr pNode)
+{
+ if (pNode) {
+ CursorEventPtr pNewEvent = pOtherEvent->cloneAs(type);
+ pNewEvent->setNode(pNode);
+ m_pEventDispatcher->sendEvent(pNewEvent);
+ }
+}
+
+void Player::handleCursorEvent(CursorEventPtr pEvent, bool bOnlyCheckCursorOver)
+{
+ // Find all nodes under the cursor.
+ vector<NodePtr> pCursorNodes;
+ DivNodePtr pEventReceiverNode = pEvent->getInputDevice()->getEventReceiverNode();
+ if (!pEventReceiverNode) {
+ pEventReceiverNode = getRootNode();
+ }
+ pEventReceiverNode->getElementsByPos(pEvent->getPos(), pCursorNodes);
+ ContactPtr pContact = pEvent->getContact();
+ if (pContact && !bOnlyCheckCursorOver) {
+ if (!pCursorNodes.empty()) {
+ NodePtr pNode = *(pCursorNodes.begin());
+ pEvent->setNode(pNode);
+ }
+ pContact->sendEventToListeners(pEvent);
+ }
+
+ int cursorID = pEvent->getCursorID();
+
+ // Determine the nodes the event should be sent to.
+ vector<NodePtr> pDestNodes = pCursorNodes;
+ if (m_EventCaptureInfoMap.find(cursorID) != m_EventCaptureInfoMap.end()) {
+ NodeWeakPtr pEventCaptureNode =
+ m_EventCaptureInfoMap[cursorID]->m_pNode;
+ if (pEventCaptureNode.expired()) {
+ m_EventCaptureInfoMap.erase(cursorID);
+ } else {
+ pDestNodes = pEventCaptureNode.lock()->getParentChain();
+ }
+ }
+
+ vector<NodePtr> pLastCursorNodes;
+ {
+ map<int, CursorStatePtr>::iterator it;
+ it = m_pLastCursorStates.find(cursorID);
+ if (it != m_pLastCursorStates.end()) {
+ pLastCursorNodes = it->second->getNodes();
+ }
+ }
+
+ // Send out events.
+ vector<NodePtr>::const_iterator itLast;
+ vector<NodePtr>::iterator itCur;
+ for (itLast = pLastCursorNodes.begin(); itLast != pLastCursorNodes.end();
+ ++itLast)
+ {
+ NodePtr pLastNode = *itLast;
+ for (itCur = pCursorNodes.begin(); itCur != pCursorNodes.end(); ++itCur) {
+ if (*itCur == pLastNode) {
+ break;
+ }
+ }
+ if (itCur == pCursorNodes.end()) {
+ sendOver(pEvent, Event::CURSOR_OUT, pLastNode);
+ }
+ }
+
+ // Send over events.
+ for (itCur = pCursorNodes.begin(); itCur != pCursorNodes.end(); ++itCur) {
+ NodePtr pCurNode = *itCur;
+ for (itLast = pLastCursorNodes.begin(); itLast != pLastCursorNodes.end();
+ ++itLast)
+ {
+ if (*itLast == pCurNode) {
+ break;
+ }
+ }
+ if (itLast == pLastCursorNodes.end()) {
+ sendOver(pEvent, Event::CURSOR_OVER, pCurNode);
+ }
+ }
+
+ if (!bOnlyCheckCursorOver) {
+ // Iterate through the nodes and send the event to all of them.
+ vector<NodePtr>::iterator it;
+ for (it = pDestNodes.begin(); it != pDestNodes.end(); ++it) {
+ NodePtr pNode = *it;
+ if (pNode->getState() != Node::NS_UNCONNECTED) {
+ pEvent->setNode(pNode);
+ if (pEvent->getType() != Event::CURSOR_MOTION) {
+ pEvent->trace();
+ }
+ if (pNode->handleEvent(pEvent) == true) {
+ // stop bubbling
+ break;
+ }
+ }
+ }
+ }
+
+ if (pEvent->getType() == Event::CURSOR_UP && pEvent->getSource() != Event::MOUSE) {
+ // Cursor has disappeared: send out events.
+ vector<NodePtr>::iterator it;
+ for (it = pCursorNodes.begin(); it != pCursorNodes.end(); ++it) {
+ NodePtr pNode = *it;
+ sendOver(pEvent, Event::CURSOR_OUT, pNode);
+ }
+ m_pLastCursorStates.erase(cursorID);
+ } else {
+ // Update list of nodes under cursor
+ if (m_pLastCursorStates.find(cursorID) != m_pLastCursorStates.end()) {
+ m_pLastCursorStates[cursorID]->setInfo(pEvent, pCursorNodes);
+ } else {
+ m_pLastCursorStates[cursorID] =
+ CursorStatePtr(new CursorState(pEvent, pCursorNodes));
+ }
+ }
+}
+
+void Player::dispatchOffscreenRendering(OffscreenCanvas* pOffscreenCanvas)
+{
+ if (!pOffscreenCanvas->getAutoRender()) {
+ return;
+ }
+ if (pOffscreenCanvas->hasRegisteredCamera()) {
+ pOffscreenCanvas->updateCameraImage();
+ while (pOffscreenCanvas->isCameraImageAvailable()) {
+ pOffscreenCanvas->doFrame(m_bPythonAvailable);
+ pOffscreenCanvas->updateCameraImage();
+ }
+ } else {
+ pOffscreenCanvas->doFrame(m_bPythonAvailable);
+ return;
+ }
+}
+
+void Player::errorIfPlaying(const std::string& sFunc) const
+{
+ if (m_bIsPlaying) {
+ throw Exception(AVG_ERR_UNSUPPORTED,
+ sFunc + " must be called before Player.play().");
+ }
+}
+
+void Player::handleTimers()
+{
+ vector<Timeout *>::iterator it;
+ m_bInHandleTimers = true;
+
+ it = m_PendingTimeouts.begin();
+ while (it != m_PendingTimeouts.end() && (*it)->isReady(getFrameTime())
+ && !m_bStopping)
+ {
+ (*it)->fire(getFrameTime());
+ if (m_bCurrentTimeoutDeleted) {
+ it = m_PendingTimeouts.begin();
+ } else {
+ if ((*it)->isInterval()) {
+ Timeout* pTempTimeout = *it;
+ it = m_PendingTimeouts.erase(it);
+ m_NewTimeouts.insert(m_NewTimeouts.begin(), pTempTimeout);
+ } else {
+ delete *it;
+ it = m_PendingTimeouts.erase(it);
+ }
+ }
+ m_bCurrentTimeoutDeleted = false;
+ }
+ for (it = m_NewTimeouts.begin(); it != m_NewTimeouts.end(); ++it) {
+ addTimeout(*it);
+ }
+ m_NewTimeouts.clear();
+
+ notifySubscribers("ON_FRAME");
+
+ m_bInHandleTimers = false;
+
+ if (m_bPythonAvailable) {
+ std::vector<Timeout *> tempAsyncCalls;
+ Py_BEGIN_ALLOW_THREADS;
+ {
+ lock_guard lock(m_AsyncCallMutex);
+ tempAsyncCalls = m_AsyncCalls;
+ m_AsyncCalls.clear();
+ }
+ Py_END_ALLOW_THREADS;
+ for (it = tempAsyncCalls.begin(); it != tempAsyncCalls.end(); ++it) {
+ (*it)->fire(getFrameTime());
+ delete *it;
+ }
+ }
+}
+
+SDLDisplayEngine * Player::getDisplayEngine() const
+{
+ return m_pDisplayEngine.get();
+}
+
+void Player::keepWindowOpen()
+{
+ m_bKeepWindowOpen = true;
+}
+
+void Player::setStopOnEscape(bool bStop)
+{
+ m_bStopOnEscape = bStop;
+}
+
+bool Player::getStopOnEscape() const
+{
+ return m_bStopOnEscape;
+}
+
+void Player::setVolume(float volume)
+{
+ m_Volume = volume;
+ if (AudioEngine::get()) {
+ AudioEngine::get()->setVolume(m_Volume);
+ }
+}
+
+float Player::getVolume() const
+{
+ return m_Volume;
+}
+
+string Player::getConfigOption(const string& sSubsys, const string& sName) const
+{
+ const string* psValue = ConfigMgr::get()->getOption(sSubsys, sName);
+ if (!psValue) {
+ throw Exception(AVG_ERR_INVALID_ARGS, string("Unknown config option ") + sSubsys
+ + ":" + sName);
+ } else {
+ return *psValue;
+ }
+}
+
+bool Player::isUsingGLES() const
+{
+ return m_GLConfig.m_bGLES;
+}
+
+bool Player::areFullShadersSupported() const
+{
+ if (!m_bIsPlaying) {
+ throw Exception(AVG_ERR_UNSUPPORTED,
+ "Must call Player.play() before areFullShadersSupported().");
+ }
+ return (m_GLConfig.m_ShaderUsage == GLConfig::FULL);
+}
+
+OffscreenCanvasPtr Player::getCanvasFromURL(const std::string& sURL)
+{
+ if (sURL.substr(0, 7) != "canvas:") {
+ throw Exception(AVG_ERR_CANT_PARSE_STRING,
+ string("Invalid canvas url :'")+sURL+"'");
+ }
+ string sCanvasID = sURL.substr(7);
+ for (unsigned i=0; i < m_pCanvases.size(); ++i) {
+ if (m_pCanvases[i]->getID() == sCanvasID) {
+ return m_pCanvases[i];
+ }
+ }
+ throw Exception(AVG_ERR_CANT_PARSE_STRING,
+ string("Canvas with url '")+sURL+"' not found.");
+}
+
+void Player::cleanup(bool bIsAbort)
+{
+ // Kill all timeouts.
+ vector<Timeout*>::iterator it;
+ for (it = m_PendingTimeouts.begin(); it != m_PendingTimeouts.end(); it++) {
+ delete *it;
+ }
+ m_PendingTimeouts.clear();
+ m_EventCaptureInfoMap.clear();
+ m_pLastCursorStates.clear();
+ m_pTestHelper->reset();
+ ThreadProfiler::get()->dumpStatistics();
+ if (m_pMainCanvas) {
+ unregisterFrameEndListener(BitmapManager::get());
+ delete BitmapManager::get();
+ m_pMainCanvas->stopPlayback(bIsAbort);
+ m_pMainCanvas = MainCanvasPtr();
+ }
+
+ if (m_pMultitouchInputDevice) {
+ m_pMultitouchInputDevice = IInputDevicePtr();
+ }
+ for (unsigned i = 0; i < m_pCanvases.size(); ++i) {
+ m_pCanvases[i]->stopPlayback(bIsAbort);
+ }
+ m_pCanvases.clear();
+
+ if (m_pDisplayEngine) {
+ m_DP.m_WindowSize = IntPoint(0,0);
+ if (!m_bKeepWindowOpen) {
+ m_pDisplayEngine->deinitRender();
+ m_pDisplayEngine->teardown();
+ m_pDisplayEngine = SDLDisplayEnginePtr();
+ }
+ }
+ if (AudioEngine::get()) {
+ AudioEngine::get()->teardown();
+ }
+ m_pEventDispatcher = EventDispatcherPtr();
+ m_pLastMouseEvent = MouseEventPtr(new MouseEvent(Event::CURSOR_MOTION, false, false,
+ false, IntPoint(-1, -1), MouseEvent::NO_BUTTON, glm::vec2(-1, -1), 0));
+
+ m_FrameTime = 0;
+ m_bIsPlaying = false;
+
+ m_CurDirName = getCWD();
+
+ removeSubscribers();
+}
+
+int Player::internalSetTimeout(int time, PyObject * pyfunc, bool bIsInterval)
+{
+ Timeout* pTimeout = new Timeout(time, pyfunc, bIsInterval, getFrameTime());
+ if (m_bInHandleTimers) {
+ m_NewTimeouts.push_back(pTimeout);
+ } else {
+ addTimeout(pTimeout);
+ }
+ return pTimeout->getID();
+}
+
+
+int Player::addTimeout(Timeout* pTimeout)
+{
+ vector<Timeout*>::iterator it = m_PendingTimeouts.begin();
+ while (it != m_PendingTimeouts.end() && (**it)<*pTimeout) {
+ it++;
+ }
+ m_PendingTimeouts.insert(it, pTimeout);
+ return pTimeout->getID();
+}
+
+void Player::setPluginPath(const string& newPath)
+{
+ PluginManager::get().setSearchPath(newPath);
+}
+
+string Player::getPluginPath() const
+{
+ return PluginManager::get().getSearchPath();
+}
+
+py::object Player::loadPlugin(const std::string& name)
+{
+ return PluginManager::get().loadPlugin(name);
+}
+
+void Player::setEventHook(PyObject * pyfunc)
+{
+ if (m_EventHookPyFunc != Py_None) {
+ Py_DECREF(m_EventHookPyFunc);
+ }
+
+ if (pyfunc != Py_None) {
+ Py_INCREF(pyfunc);
+ }
+
+ m_EventHookPyFunc = pyfunc;
+}
+
+PyObject * Player::getEventHook() const
+{
+ return m_EventHookPyFunc;
+}
+
+Player::EventCaptureInfo::EventCaptureInfo(const NodeWeakPtr& pNode)
+ : m_pNode(pNode),
+ m_CaptureCount(1)
+{
+}
+
+}