cleanup Agent recording handling, make loadRecording async

This commit is contained in:
Stephen Birarda 2017-04-04 10:56:25 -07:00
parent 6ed4295b8b
commit c7a28a527a
10 changed files with 107 additions and 37 deletions

View file

@ -33,6 +33,7 @@
#include <ScriptEngines.h> #include <ScriptEngines.h>
#include <UUID.h> #include <UUID.h>
#include <recording/ClipCache.h>
#include <recording/Deck.h> #include <recording/Deck.h>
#include <recording/Recorder.h> #include <recording/Recorder.h>
#include <recording/Frame.h> #include <recording/Frame.h>
@ -53,7 +54,8 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10;
Agent::Agent(ReceivedMessage& message) : Agent::Agent(ReceivedMessage& message) :
ThreadedAssignment(message), ThreadedAssignment(message),
_receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES) { _receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES)
{
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender); DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
ResourceManager::init(); ResourceManager::init();
@ -64,12 +66,16 @@ Agent::Agent(ReceivedMessage& message) :
DependencyManager::set<SoundCache>(); DependencyManager::set<SoundCache>();
DependencyManager::set<AudioScriptingInterface>(); DependencyManager::set<AudioScriptingInterface>();
DependencyManager::set<AudioInjectorManager>(); DependencyManager::set<AudioInjectorManager>();
DependencyManager::set<recording::Deck>(); DependencyManager::set<recording::Deck>();
DependencyManager::set<recording::Recorder>(); DependencyManager::set<recording::Recorder>();
DependencyManager::set<RecordingScriptingInterface>(); DependencyManager::set<recording::ClipCache>();
DependencyManager::set<ScriptCache>(); DependencyManager::set<ScriptCache>();
DependencyManager::set<ScriptEngines>(ScriptEngine::AGENT_SCRIPT); DependencyManager::set<ScriptEngines>(ScriptEngine::AGENT_SCRIPT);
DependencyManager::set<RecordingScriptingInterface>();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes( packetReceiver.registerListenerForTypes(
@ -327,6 +333,8 @@ void Agent::executeScript() {
_scriptEngine = std::unique_ptr<ScriptEngine>(new ScriptEngine(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload)); _scriptEngine = std::unique_ptr<ScriptEngine>(new ScriptEngine(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload));
_scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do _scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do
DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(_scriptEngine.get());
// setup an Avatar for the script to use // setup an Avatar for the script to use
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>(); auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
@ -470,6 +478,8 @@ void Agent::executeScript() {
Frame::clearFrameHandler(AUDIO_FRAME_TYPE); Frame::clearFrameHandler(AUDIO_FRAME_TYPE);
Frame::clearFrameHandler(AVATAR_FRAME_TYPE); Frame::clearFrameHandler(AVATAR_FRAME_TYPE);
DependencyManager::destroy<RecordingScriptingInterface>();
setFinished(true); setFinished(true);
} }
@ -753,8 +763,19 @@ void Agent::aboutToFinish() {
// cleanup the AudioInjectorManager (and any still running injectors) // cleanup the AudioInjectorManager (and any still running injectors)
DependencyManager::destroy<AudioInjectorManager>(); DependencyManager::destroy<AudioInjectorManager>();
// destroy all other created dependencies
DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<ScriptEngines>(); DependencyManager::destroy<ScriptEngines>();
DependencyManager::destroy<ResourceCacheSharedItems>();
DependencyManager::destroy<SoundCache>();
DependencyManager::destroy<AudioScriptingInterface>();
DependencyManager::destroy<recording::Deck>();
DependencyManager::destroy<recording::Recorder>();
DependencyManager::destroy<recording::ClipCache>();
emit stopAvatarAudioTimer(); emit stopAvatarAudioTimer();
_avatarAudioTimerThread.quit(); _avatarAudioTimerThread.quit();

View file

@ -46,9 +46,6 @@ class Agent : public ThreadedAssignment {
public: public:
Agent(ReceivedMessage& message); Agent(ReceivedMessage& message);
void setIsAvatar(bool isAvatar);
bool isAvatar() const { return _isAvatar; }
bool isPlayingAvatarSound() const { return _avatarSound != NULL; } bool isPlayingAvatarSound() const { return _avatarSound != NULL; }
bool isListeningToAudioStream() const { return _isListeningToAudioStream; } bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
@ -65,6 +62,9 @@ public:
public slots: public slots:
void run() override; void run() override;
void playAvatarSound(SharedSoundPointer avatarSound); void playAvatarSound(SharedSoundPointer avatarSound);
void setIsAvatar(bool isAvatar);
bool isAvatar() const { return _isAvatar; }
private slots: private slots:
void requestScript(); void requestScript();

View file

@ -118,6 +118,7 @@
#include <udt/PacketHeaders.h> #include <udt/PacketHeaders.h>
#include <UserActivityLogger.h> #include <UserActivityLogger.h>
#include <UsersScriptingInterface.h> #include <UsersScriptingInterface.h>
#include <recording/ClipCache.h>
#include <recording/Deck.h> #include <recording/Deck.h>
#include <recording/Recorder.h> #include <recording/Recorder.h>
#include <shared/StringHelpers.h> #include <shared/StringHelpers.h>
@ -464,6 +465,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<StatTracker>(); DependencyManager::set<StatTracker>();
DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT); DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT);
DependencyManager::set<Preferences>(); DependencyManager::set<Preferences>();
DependencyManager::set<recording::ClipCache>();
DependencyManager::set<recording::Deck>(); DependencyManager::set<recording::Deck>();
DependencyManager::set<recording::Recorder>(); DependencyManager::set<recording::Recorder>();
DependencyManager::set<AddressManager>(); DependencyManager::set<AddressManager>();
@ -5437,6 +5439,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
// AvatarManager has some custom types // AvatarManager has some custom types
AvatarManager::registerMetaTypes(scriptEngine); AvatarManager::registerMetaTypes(scriptEngine);
// give the script engine to the RecordingScriptingInterface for its callbacks
DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(scriptEngine);
if (property(hifi::properties::TEST).isValid()) { if (property(hifi::properties::TEST).isValid()) {
scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance());
} }

View file

@ -19,7 +19,6 @@ QThreadStorage<QNetworkAccessManager*> networkAccessManagers;
QNetworkAccessManager& NetworkAccessManager::getInstance() { QNetworkAccessManager& NetworkAccessManager::getInstance() {
if (!networkAccessManagers.hasLocalData()) { if (!networkAccessManagers.hasLocalData()) {
networkAccessManagers.setLocalData(new QNetworkAccessManager()); networkAccessManagers.setLocalData(new QNetworkAccessManager());
} }
return *networkAccessManagers.localData(); return *networkAccessManagers.localData();

View file

@ -5,6 +5,9 @@
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <QThread>
#include "ClipCache.h" #include "ClipCache.h"
#include "impl/PointerClip.h" #include "impl/PointerClip.h"
@ -21,18 +24,28 @@ void NetworkClip::init(const QByteArray& clipData) {
void NetworkClipLoader::downloadFinished(const QByteArray& data) { void NetworkClipLoader::downloadFinished(const QByteArray& data) {
_clip->init(data); _clip->init(data);
finishedLoading(true); finishedLoading(true);
emit clipLoaded();
} }
ClipCache& ClipCache::instance() { ClipCache::ClipCache(QObject* parent) :
static ClipCache _instance; ResourceCache(parent)
return _instance; {
} }
NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) {
return ResourceCache::getResource(url, QUrl(), nullptr).staticCast<NetworkClipLoader>(); if (QThread::currentThread() != thread()) {
NetworkClipLoaderPointer result;
QMetaObject::invokeMethod(this, "getClipLoader", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(NetworkClipLoaderPointer, result), Q_ARG(const QUrl&, url));
return result;
}
return getResource(url).staticCast<NetworkClipLoader>();
} }
QSharedPointer<Resource> ClipCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, const void* extra) { QSharedPointer<Resource> ClipCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, const void* extra) {
qDebug() << "Loading recording at" << url;
return QSharedPointer<Resource>(new NetworkClipLoader(url), &Resource::deleter); return QSharedPointer<Resource>(new NetworkClipLoader(url), &Resource::deleter);
} }

View file

@ -30,26 +30,34 @@ private:
}; };
class NetworkClipLoader : public Resource { class NetworkClipLoader : public Resource {
Q_OBJECT
public: public:
NetworkClipLoader(const QUrl& url); NetworkClipLoader(const QUrl& url);
virtual void downloadFinished(const QByteArray& data) override; virtual void downloadFinished(const QByteArray& data) override;
ClipPointer getClip() { return _clip; } ClipPointer getClip() { return _clip; }
bool completed() { return _failedToLoad || isLoaded(); } bool completed() { return _failedToLoad || isLoaded(); }
signals:
void clipLoaded();
private: private:
const NetworkClip::Pointer _clip; const NetworkClip::Pointer _clip;
}; };
using NetworkClipLoaderPointer = QSharedPointer<NetworkClipLoader>; using NetworkClipLoaderPointer = QSharedPointer<NetworkClipLoader>;
class ClipCache : public ResourceCache { class ClipCache : public ResourceCache, public Dependency {
public: Q_OBJECT
static ClipCache& instance(); SINGLETON_DEPENDENCY
public slots:
NetworkClipLoaderPointer getClipLoader(const QUrl& url); NetworkClipLoaderPointer getClipLoader(const QUrl& url);
protected: protected:
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, const void* extra) override; virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, const void* extra) override;
private:
ClipCache(QObject* parent = nullptr);
}; };
} }

View file

@ -52,32 +52,48 @@ float RecordingScriptingInterface::playerLength() const {
return _player->length(); return _player->length();
} }
bool RecordingScriptingInterface::loadRecording(const QString& url) { void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue callback) {
using namespace recording; auto clipLoader = DependencyManager::get<recording::ClipCache>()->getClipLoader(url);
auto loader = ClipCache::instance().getClipLoader(url); // hold a strong pointer to the loading clip so that it has a chance to load
if (!loader) { _clipLoaders.insert(clipLoader);
qWarning() << "Clip failed to load from " << url;
return false;
}
if (!loader->isLoaded()) { auto weakClipLoader = clipLoader.toWeakRef();
QEventLoop loop;
QObject::connect(loader.data(), &Resource::loaded, &loop, &QEventLoop::quit);
QObject::connect(loader.data(), &Resource::failed, &loop, &QEventLoop::quit);
loop.exec();
}
if (!loader->isLoaded()) { // when clip loaded, call the callback with the URL and success boolean
qWarning() << "Clip failed to load from " << url; connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, this, [this, weakClipLoader, url, callback]() mutable {
return false;
}
_player->queueClip(loader->getClip()); if (auto clipLoader = weakClipLoader.toStrongRef()) {
return true; qCDebug(scriptengine) << "Loaded recording from" << url;
_player->queueClip(clipLoader->getClip());
if (callback.isFunction()) {
QScriptValueList args { true, url };
callback.call(_scriptEngine->globalObject(), args);
}
// drop our strong pointer to this clip so it is cleaned up
_clipLoaders.remove(clipLoader);
}
});
// when clip load fails, call the callback with the URL and failure boolean
connect(clipLoader.data(), &recording::NetworkClipLoader::failed, this, [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable {
qCDebug(scriptengine) << "Failed to load recording from" << url;
if (callback.isFunction()) {
QScriptValueList args { false, url };
callback.call(_scriptEngine->currentContext()->thisObject(), args);
}
if (auto clipLoader = weakClipLoader.toStrongRef()) {
// drop out strong pointer to this clip so it is cleaned up
_clipLoaders.remove(clipLoader);
}
});
} }
void RecordingScriptingInterface::startPlaying() { void RecordingScriptingInterface::startPlaying() {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection); QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection);

View file

@ -15,9 +15,11 @@
#include <QtCore/QObject> #include <QtCore/QObject>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <recording/ClipCache.h>
#include <recording/Forward.h> #include <recording/Forward.h>
#include <recording/Frame.h> #include <recording/Frame.h>
class QScriptEngine;
class QScriptValue; class QScriptValue;
class RecordingScriptingInterface : public QObject, public Dependency { class RecordingScriptingInterface : public QObject, public Dependency {
@ -26,8 +28,11 @@ class RecordingScriptingInterface : public QObject, public Dependency {
public: public:
RecordingScriptingInterface(); RecordingScriptingInterface();
void setScriptEngine(QScriptEngine* scriptEngine) { _scriptEngine = scriptEngine; }
public slots: public slots:
bool loadRecording(const QString& url);
void loadRecording(const QString& url, QScriptValue callback = QScriptValue());
void startPlaying(); void startPlaying();
void pausePlayer(); void pausePlayer();
@ -79,6 +84,9 @@ protected:
Flag _useAttachments { false }; Flag _useAttachments { false };
Flag _useSkeletonModel { false }; Flag _useSkeletonModel { false };
recording::ClipPointer _lastClip; recording::ClipPointer _lastClip;
QScriptEngine* _scriptEngine;
QSet<recording::NetworkClipLoaderPointer> _clipLoaders;
}; };
#endif // hifi_RecordingScriptingInterface_h #endif // hifi_RecordingScriptingInterface_h

View file

@ -21,7 +21,7 @@
#include <typeinfo> #include <typeinfo>
#define SINGLETON_DEPENDENCY \ #define SINGLETON_DEPENDENCY \
friend class DependencyManager; friend class ::DependencyManager;
class Dependency { class Dependency {
public: public:

View file

@ -769,7 +769,7 @@ bool similarStrings(const QString& stringA, const QString& stringB) {
void disableQtBearerPoll() { void disableQtBearerPoll() {
// to work around the Qt constant wireless scanning, set the env for polling interval very high // to work around the Qt constant wireless scanning, set the env for polling interval very high
const QByteArray EXTREME_BEARER_POLL_TIMEOUT = QString::number(INT_MAX).toLocal8Bit(); const QByteArray EXTREME_BEARER_POLL_TIMEOUT = QString::number(INT16_MAX).toLocal8Bit();
qputenv("QT_BEARER_POLL_TIMEOUT", EXTREME_BEARER_POLL_TIMEOUT); qputenv("QT_BEARER_POLL_TIMEOUT", EXTREME_BEARER_POLL_TIMEOUT);
} }