From c7a28a527a2f487ad5534189e17690d3231586ba Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 4 Apr 2017 10:56:25 -0700 Subject: [PATCH 1/8] cleanup Agent recording handling, make loadRecording async --- assignment-client/src/Agent.cpp | 25 ++++++++- assignment-client/src/Agent.h | 6 +- interface/src/Application.cpp | 5 ++ .../networking/src/NetworkAccessManager.cpp | 1 - .../recording/src/recording/ClipCache.cpp | 21 +++++-- libraries/recording/src/recording/ClipCache.h | 16 ++++-- .../src/RecordingScriptingInterface.cpp | 56 ++++++++++++------- .../src/RecordingScriptingInterface.h | 10 +++- libraries/shared/src/DependencyManager.h | 2 +- libraries/shared/src/SharedUtil.cpp | 2 +- 10 files changed, 107 insertions(+), 37 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index e28c04379f..561d9aa209 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -53,7 +54,8 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; Agent::Agent(ReceivedMessage& 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()->setPacketSender(&_entityEditSender); ResourceManager::init(); @@ -64,12 +66,16 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(ScriptEngine::AGENT_SCRIPT); + DependencyManager::set(); + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListenerForTypes( @@ -327,6 +333,8 @@ void Agent::executeScript() { _scriptEngine = std::unique_ptr(new ScriptEngine(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload)); _scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do + DependencyManager::get()->setScriptEngine(_scriptEngine.get()); + // setup an Avatar for the script to use auto scriptedAvatar = DependencyManager::get(); @@ -470,6 +478,8 @@ void Agent::executeScript() { Frame::clearFrameHandler(AUDIO_FRAME_TYPE); Frame::clearFrameHandler(AVATAR_FRAME_TYPE); + DependencyManager::destroy(); + setFinished(true); } @@ -753,8 +763,19 @@ void Agent::aboutToFinish() { // cleanup the AudioInjectorManager (and any still running injectors) DependencyManager::destroy(); + + // destroy all other created dependencies + DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); + + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); + emit stopAvatarAudioTimer(); _avatarAudioTimerThread.quit(); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 620ac8e047..2a156aba18 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -46,9 +46,6 @@ class Agent : public ThreadedAssignment { public: Agent(ReceivedMessage& message); - void setIsAvatar(bool isAvatar); - bool isAvatar() const { return _isAvatar; } - bool isPlayingAvatarSound() const { return _avatarSound != NULL; } bool isListeningToAudioStream() const { return _isListeningToAudioStream; } @@ -65,6 +62,9 @@ public: public slots: void run() override; void playAvatarSound(SharedSoundPointer avatarSound); + + void setIsAvatar(bool isAvatar); + bool isAvatar() const { return _isAvatar; } private slots: void requestScript(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 62917b304c..781d866c0f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -118,6 +118,7 @@ #include #include #include +#include #include #include #include @@ -464,6 +465,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(ScriptEngine::CLIENT_SCRIPT); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -5437,6 +5439,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri // AvatarManager has some custom types AvatarManager::registerMetaTypes(scriptEngine); + // give the script engine to the RecordingScriptingInterface for its callbacks + DependencyManager::get()->setScriptEngine(scriptEngine); + if (property(hifi::properties::TEST).isValid()) { scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance()); } diff --git a/libraries/networking/src/NetworkAccessManager.cpp b/libraries/networking/src/NetworkAccessManager.cpp index 78a05677fd..73096825e0 100644 --- a/libraries/networking/src/NetworkAccessManager.cpp +++ b/libraries/networking/src/NetworkAccessManager.cpp @@ -19,7 +19,6 @@ QThreadStorage networkAccessManagers; QNetworkAccessManager& NetworkAccessManager::getInstance() { if (!networkAccessManagers.hasLocalData()) { networkAccessManagers.setLocalData(new QNetworkAccessManager()); - } return *networkAccessManagers.localData(); diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp index 66d5fb0d88..0665608bad 100644 --- a/libraries/recording/src/recording/ClipCache.cpp +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -5,6 +5,9 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + +#include + #include "ClipCache.h" #include "impl/PointerClip.h" @@ -21,18 +24,28 @@ void NetworkClip::init(const QByteArray& clipData) { void NetworkClipLoader::downloadFinished(const QByteArray& data) { _clip->init(data); finishedLoading(true); + emit clipLoaded(); } -ClipCache& ClipCache::instance() { - static ClipCache _instance; - return _instance; +ClipCache::ClipCache(QObject* parent) : + ResourceCache(parent) +{ + } NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { - return ResourceCache::getResource(url, QUrl(), nullptr).staticCast(); + 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(); } QSharedPointer ClipCache::createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) { + qDebug() << "Loading recording at" << url; return QSharedPointer(new NetworkClipLoader(url), &Resource::deleter); } diff --git a/libraries/recording/src/recording/ClipCache.h b/libraries/recording/src/recording/ClipCache.h index 7df83efdbf..2c3465e725 100644 --- a/libraries/recording/src/recording/ClipCache.h +++ b/libraries/recording/src/recording/ClipCache.h @@ -30,26 +30,34 @@ private: }; class NetworkClipLoader : public Resource { + Q_OBJECT public: NetworkClipLoader(const QUrl& url); virtual void downloadFinished(const QByteArray& data) override; ClipPointer getClip() { return _clip; } bool completed() { return _failedToLoad || isLoaded(); } +signals: + void clipLoaded(); + private: const NetworkClip::Pointer _clip; }; using NetworkClipLoaderPointer = QSharedPointer; -class ClipCache : public ResourceCache { -public: - static ClipCache& instance(); - +class ClipCache : public ResourceCache, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public slots: NetworkClipLoaderPointer getClipLoader(const QUrl& url); protected: virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) override; + +private: + ClipCache(QObject* parent = nullptr); }; } diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index 41bb780b47..e76a3859f0 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -52,32 +52,48 @@ float RecordingScriptingInterface::playerLength() const { return _player->length(); } -bool RecordingScriptingInterface::loadRecording(const QString& url) { - using namespace recording; +void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue callback) { + auto clipLoader = DependencyManager::get()->getClipLoader(url); - auto loader = ClipCache::instance().getClipLoader(url); - if (!loader) { - qWarning() << "Clip failed to load from " << url; - return false; - } + // hold a strong pointer to the loading clip so that it has a chance to load + _clipLoaders.insert(clipLoader); - if (!loader->isLoaded()) { - QEventLoop loop; - QObject::connect(loader.data(), &Resource::loaded, &loop, &QEventLoop::quit); - QObject::connect(loader.data(), &Resource::failed, &loop, &QEventLoop::quit); - loop.exec(); - } + auto weakClipLoader = clipLoader.toWeakRef(); - if (!loader->isLoaded()) { - qWarning() << "Clip failed to load from " << url; - return false; - } + // when clip loaded, call the callback with the URL and success boolean + connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, this, [this, weakClipLoader, url, callback]() mutable { - _player->queueClip(loader->getClip()); - return true; + if (auto clipLoader = weakClipLoader.toStrongRef()) { + 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() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection); diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index 9661ee9d80..f6d335b17d 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -15,9 +15,11 @@ #include #include +#include #include #include +class QScriptEngine; class QScriptValue; class RecordingScriptingInterface : public QObject, public Dependency { @@ -26,8 +28,11 @@ class RecordingScriptingInterface : public QObject, public Dependency { public: RecordingScriptingInterface(); + void setScriptEngine(QScriptEngine* scriptEngine) { _scriptEngine = scriptEngine; } + public slots: - bool loadRecording(const QString& url); + + void loadRecording(const QString& url, QScriptValue callback = QScriptValue()); void startPlaying(); void pausePlayer(); @@ -79,6 +84,9 @@ protected: Flag _useAttachments { false }; Flag _useSkeletonModel { false }; recording::ClipPointer _lastClip; + + QScriptEngine* _scriptEngine; + QSet _clipLoaders; }; #endif // hifi_RecordingScriptingInterface_h diff --git a/libraries/shared/src/DependencyManager.h b/libraries/shared/src/DependencyManager.h index 719763f706..7a453e63c3 100644 --- a/libraries/shared/src/DependencyManager.h +++ b/libraries/shared/src/DependencyManager.h @@ -21,7 +21,7 @@ #include #define SINGLETON_DEPENDENCY \ - friend class DependencyManager; + friend class ::DependencyManager; class Dependency { public: diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index a7e251f0e0..81a0eed9f3 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -769,7 +769,7 @@ bool similarStrings(const QString& stringA, const QString& stringB) { void disableQtBearerPoll() { // 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); } From 1b245387565bd141ce467bf7f392d584ef8fe1ef Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 5 Apr 2017 11:18:52 -0700 Subject: [PATCH 2/8] modify scripts to use new recording API --- .../BetterClientSimulationBotFromRecording.js | 12 ++++--- script-archive/acScripts/PlayRecordingOnAC.js | 10 +++--- .../acScripts/triggeredRecordingOnAC.js | 11 +++--- script-archive/tests/playbackAcTest.js | 12 ++++--- script-archive/vrShop/cash/shopCashierAC.js | 25 +++++++------ .../vrShop/review/shopReviewerAC.js | 34 ++++++++++-------- .../developer/utilities/record/recorder.js | 35 ++++++++++--------- 7 files changed, 80 insertions(+), 59 deletions(-) diff --git a/script-archive/acScripts/BetterClientSimulationBotFromRecording.js b/script-archive/acScripts/BetterClientSimulationBotFromRecording.js index fafb761729..c276d302e1 100644 --- a/script-archive/acScripts/BetterClientSimulationBotFromRecording.js +++ b/script-archive/acScripts/BetterClientSimulationBotFromRecording.js @@ -138,7 +138,13 @@ Agent.isAvatar = true; Agent.isListeningToAudioStream = true; Avatar.skeletonModelURL = AVATAR_URL; // FIXME - currently setting an avatar while playing a recording doesn't work it will be ignored -Recording.loadRecording(RECORDING_URL); +Recording.loadRecording(RECORDING_URL, function(success) { + if (success) { + Script.update.connect(update); + } else { + print("Failed to load recording from " + RECORDING_URL); + } +}); count = 300; // This is necessary to wait for the audio mixer to connect function update(event) { @@ -174,10 +180,8 @@ function update(event) { +" FT: " + Avatar.getDataRate("faceTrackerOutbound").toFixed(2) + "\n" +" JD: " + Avatar.getDataRate("jointDataOutbound").toFixed(2)); } - + if (!Recording.isPlaying()) { Script.update.disconnect(update); } } - -Script.update.connect(update); diff --git a/script-archive/acScripts/PlayRecordingOnAC.js b/script-archive/acScripts/PlayRecordingOnAC.js index 0961f65079..015f247e4a 100644 --- a/script-archive/acScripts/PlayRecordingOnAC.js +++ b/script-archive/acScripts/PlayRecordingOnAC.js @@ -20,7 +20,11 @@ Avatar.orientation = Quat.fromPitchYawRollDegrees(0, 0, 0); Avatar.scale = 1.0; Agent.isAvatar = true; -Recording.loadRecording(recordingFile); +Recording.loadRecording(recordingFile, function(success) { + if (success) { + Script.update.connect(update); + } +}); count = 300; // This is necessary to wait for the audio mixer to connect function update(event) { @@ -39,10 +43,8 @@ function update(event) { Vec3.print("Playing from ", Avatar.position); count--; } - + if (!Recording.isPlaying()) { Script.update.disconnect(update); } } - -Script.update.connect(update); diff --git a/script-archive/acScripts/triggeredRecordingOnAC.js b/script-archive/acScripts/triggeredRecordingOnAC.js index 9de5173615..c3a87bc44e 100644 --- a/script-archive/acScripts/triggeredRecordingOnAC.js +++ b/script-archive/acScripts/triggeredRecordingOnAC.js @@ -42,10 +42,13 @@ var playRecording = function() { Recording.setPlayerLoop(false); Recording.setPlayerTime(STARTING_TIME); Recording.setPlayerAudioOffset(AUDIO_OFFSET); - Recording.loadRecording(CLIP_URL); - Recording.startPlaying(); - isPlaying = true; - isPlayable = false; // Set this true again after the cooldown period + Recording.loadRecording(CLIP_URL, function(success) { + if (success) { + Recording.startPlaying(); + isPlaying = true; + isPlayable = false; // Set this true again after the cooldown period + } + }); }; Script.update.connect(function(deltaTime) { diff --git a/script-archive/tests/playbackAcTest.js b/script-archive/tests/playbackAcTest.js index 5630b17ed8..51ff1bdf02 100644 --- a/script-archive/tests/playbackAcTest.js +++ b/script-archive/tests/playbackAcTest.js @@ -2,7 +2,7 @@ var origin = {x: 512, y: 512, z: 512}; var millisecondsToWaitBeforeStarting = 2 * 1000; // To give the various servers a chance to start. -var millisecondsToWaitBeforeEnding = 30 * 1000; +var millisecondsToWaitBeforeEnding = 30 * 1000; Avatar.skeletonModelURL = "https://hifi-public.s3.amazonaws.com/marketplace/contents/dd03b8e3-52fb-4ab3-9ac9-3b17e00cd85d/98baa90b3b66803c5d7bd4537fca6993.fst"; //lovejoy Avatar.displayName = "AC Avatar"; @@ -10,9 +10,11 @@ Agent.isAvatar = true; Script.setTimeout(function () { Avatar.position = origin; - Recording.loadRecording("d:/hifi.rec"); - Recording.setPlayerLoop(true); - Recording.startPlaying(); + Recording.loadRecording("d:/hifi.rec", function(success){ + Recording.setPlayerLoop(true); + Recording.startPlaying(); + }); + }, millisecondsToWaitBeforeStarting); @@ -21,4 +23,4 @@ Script.setTimeout(function () { Agent.isAvatar = false; Recording.stopPlaying(); Script.stop(); -}, millisecondsToWaitBeforeEnding); \ No newline at end of file +}, millisecondsToWaitBeforeEnding); diff --git a/script-archive/vrShop/cash/shopCashierAC.js b/script-archive/vrShop/cash/shopCashierAC.js index 1219806e8a..91b45484fd 100644 --- a/script-archive/vrShop/cash/shopCashierAC.js +++ b/script-archive/vrShop/cash/shopCashierAC.js @@ -26,7 +26,7 @@ var PLAY = "Play"; function getAction(channel, message, senderID) { if(subscribed) { print("I'm the agent and I received this: " + message); - + switch(message) { case PLAY: print("Play"); @@ -35,7 +35,7 @@ function getAction(channel, message, senderID) { Recording.startPlaying(); } break; - + default: print("Unknown action: " + action); break; @@ -49,16 +49,19 @@ function update(deltaTime) { totalTime += deltaTime; if (totalTime > WAIT_FOR_AUDIO_MIXER) { - if (!subscribed) { + if (!subscribed) { Messages.subscribe(PLAYBACK_CHANNEL); subscribed = true; - Recording.loadRecording(clip_url); - Recording.setPlayFromCurrentLocation(playFromCurrentLocation); - Recording.setPlayerUseDisplayName(useDisplayName); - Recording.setPlayerUseAttachments(useAttachments); - Recording.setPlayerUseHeadModel(false); - Recording.setPlayerUseSkeletonModel(useAvatarModel); - Agent.isAvatar = true; + Recording.loadRecording(clip_url, function(success) { + if (success) { + Recording.setPlayFromCurrentLocation(playFromCurrentLocation); + Recording.setPlayerUseDisplayName(useDisplayName); + Recording.setPlayerUseAttachments(useAttachments); + Recording.setPlayerUseHeadModel(false); + Recording.setPlayerUseSkeletonModel(useAvatarModel); + Agent.isAvatar = true; + } + }); } } @@ -70,4 +73,4 @@ Messages.messageReceived.connect(function (channel, message, senderID) { } }); -Script.update.connect(update); \ No newline at end of file +Script.update.connect(update); diff --git a/script-archive/vrShop/review/shopReviewerAC.js b/script-archive/vrShop/review/shopReviewerAC.js index 7b7776fe98..c39aaa2f4a 100644 --- a/script-archive/vrShop/review/shopReviewerAC.js +++ b/script-archive/vrShop/review/shopReviewerAC.js @@ -9,7 +9,7 @@ var command = null; -var clip_url = null; +var clip_url = null; var REVIEW_CHANNEL = "reviewChannel"; var playFromCurrentLocation = true; @@ -28,16 +28,16 @@ var HIDE = "Hide"; function getAction(channel, message, senderID) { if(subscribed) { print("I'm the agent and I received this: " + message); - + if (Recording.isPlaying()) { Recording.stopPlaying(); } - + m = JSON.parse(message); - + command = m.command; clip_url = m.clip_url; - + switch(command) { case PLAY: print("Play"); @@ -46,21 +46,25 @@ function getAction(channel, message, senderID) { Recording.startPlaying(); } break; - + case SHOW: print("Show"); - Recording.loadRecording(clip_url); - Agent.isAvatar = true; - Recording.setPlayerTime(0.0); - Recording.startPlaying(); - Recording.stopPlaying(); + Recording.loadRecording(clip_url, function(success){ + if (success) { + Agent.isAvatar = true; + Recording.setPlayerTime(0.0); + Recording.startPlaying(); + Recording.stopPlaying(); + } + }); + break; - + case HIDE: print("Hide"); Agent.isAvatar = false; break; - + default: print("Unknown action: " + action); break; @@ -74,7 +78,7 @@ function update(deltaTime) { totalTime += deltaTime; if (totalTime > WAIT_FOR_AUDIO_MIXER) { - if (!subscribed) { + if (!subscribed) { Messages.subscribe(REVIEW_CHANNEL); subscribed = true; Recording.setPlayFromCurrentLocation(playFromCurrentLocation); @@ -93,4 +97,4 @@ Messages.messageReceived.connect(function (channel, message, senderID) { } }); -Script.update.connect(update); \ No newline at end of file +Script.update.connect(update); diff --git a/scripts/developer/utilities/record/recorder.js b/scripts/developer/utilities/record/recorder.js index ba1c8b0393..b335274372 100644 --- a/scripts/developer/utilities/record/recorder.js +++ b/scripts/developer/utilities/record/recorder.js @@ -55,13 +55,13 @@ function setupToolBar() { } Tool.IMAGE_HEIGHT /= 2; Tool.IMAGE_WIDTH /= 2; - + toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL); - + toolBar.onMove = onToolbarMove; toolBar.setBack(COLOR_TOOL_BAR, ALPHA_OFF); - + recordIcon = toolBar.addTool({ imageURL: TOOL_ICON_URL + "recording-record.svg", subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, @@ -71,7 +71,7 @@ function setupToolBar() { alpha: Recording.isPlaying() ? ALPHA_OFF : ALPHA_ON, visible: true }, true, !Recording.isRecording()); - + var playLoopWidthFactor = 1.65; playIcon = toolBar.addTool({ imageURL: TOOL_ICON_URL + "play-pause.svg", @@ -80,7 +80,7 @@ function setupToolBar() { alpha: (Recording.isRecording() || Recording.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON, visible: true }, false); - + playLoopIcon = toolBar.addTool({ imageURL: TOOL_ICON_URL + "play-and-loop.svg", subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, @@ -89,10 +89,10 @@ function setupToolBar() { alpha: (Recording.isRecording() || Recording.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON, visible: true }, false); - + timerOffset = toolBar.width + ToolBar.SPACING; spacing = toolBar.addSpacing(0); - + saveIcon = toolBar.addTool({ imageURL: TOOL_ICON_URL + "recording-save.svg", width: Tool.IMAGE_WIDTH, @@ -100,7 +100,7 @@ function setupToolBar() { alpha: (Recording.isRecording() || Recording.isPlaying() || Recording.playerLength() === 0) ? ALPHA_OFF : ALPHA_ON, visible: true }, false); - + loadIcon = toolBar.addTool({ imageURL: TOOL_ICON_URL + "recording-upload.svg", width: Tool.IMAGE_WIDTH, @@ -153,10 +153,10 @@ function onToolbarMove(newX, newY, deltaX, deltaY) { x: newX + timerOffset - ToolBar.SPACING, y: newY }); - + slider.x = newX - ToolBar.SPACING; slider.y = newY - slider.h - ToolBar.SPACING; - + Overlays.editOverlay(slider.background, { x: slider.x, y: slider.y @@ -182,13 +182,13 @@ function updateTimer() { width: timerWidth }); toolBar.changeSpacing(timerWidth + ToolBar.SPACING, spacing); - + if (Recording.isRecording()) { slider.pos = 1.0; } else if (Recording.playerLength() > 0) { slider.pos = Recording.playerElapsed() / Recording.playerLength(); } - + Overlays.editOverlay(slider.foreground, { width: slider.pos * slider.w }); @@ -221,7 +221,7 @@ function moveUI() { function mousePressEvent(event) { var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - + if (recordIcon === toolBar.clicked(clickedOverlay, false) && !Recording.isPlaying()) { if (!Recording.isRecording()) { Recording.startRecording(); @@ -281,8 +281,11 @@ function mousePressEvent(event) { if (!Recording.isRecording() && !Recording.isPlaying()) { recordingFile = Window.browse("Load recording from file", ".", "Recordings (*.hfr *.rec *.HFR *.REC)"); if (!(recordingFile === "null" || recordingFile === null || recordingFile === "")) { - Recording.loadRecording(recordingFile); - setDefaultPlayerOptions(); + Recording.loadRecording(recordingFile, function(success) { + if (success) { + setDefaultPlayerOptions(); + } + }); } if (Recording.playerLength() > 0) { toolBar.setAlpha(ALPHA_ON, playIcon); @@ -323,7 +326,7 @@ function update() { } updateTimer(); - + if (watchStop && !Recording.isPlaying()) { watchStop = false; toolBar.setAlpha(ALPHA_ON, recordIcon); From e364b8d33e5ee01528b377a9904fc64d4945b80d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 13 Apr 2017 13:35:59 -0700 Subject: [PATCH 3/8] avatar-mixer resends to avoid stale avatar --- .../src/avatars/AvatarMixerSlave.cpp | 21 ++++++++++++++----- libraries/avatars/src/AvatarData.h | 7 +++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index c4497a1066..a4e9b608de 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -175,6 +175,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { } }); + const uint64_t MIN_KEEP_ALIVE_PERIOD_FOR_OTHER_AVATAR = (AVATAR_UPDATE_TIMEOUT - 1) * USECS_PER_SECOND; AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer(); ViewFrustum cameraView = nodeData->getViewFrustom(); std::priority_queue sortedAvatars; @@ -262,11 +263,17 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // have 15 (45hz-30hz) duplicate frames. In this case, the stat // avg_other_av_skips_per_second does report 15. // - // make sure we haven't already sent this data from this sender to this receiver + // make sure we haven't already sent this data from this sender to that receiver // or that somehow we haven't sent if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { - ++numAvatarsHeldBack; - shouldIgnore = true; + // don't ignore this avatar if we haven't sent any update for a long while + uint64_t lastBroadcastTime = nodeData->getLastBroadcastTime(avatarNode->getUUID()); + const AvatarMixerClientData* otherNodeData = reinterpret_cast(avatarNode->getLinkedData()); + if (lastBroadcastTime > otherNodeData->getIdentityChangeTimestamp() && + lastBroadcastTime > startIgnoreCalculation - MIN_KEEP_ALIVE_PERIOD_FOR_OTHER_AVATAR) { + ++numAvatarsHeldBack; + shouldIgnore = true; + } } else if (lastSeqFromSender - lastSeqToReceiver > 1) { // this is a skip - we still send the packet but capture the presence of the skip so we see it happening ++numAvatarsWithSkippedFrames; @@ -302,8 +309,12 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO - // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. - if (nodeData->getLastBroadcastTime(otherNode->getUUID()) <= otherNodeData->getIdentityChangeTimestamp()) { + // the time that Avatar B flagged an IDENTITY DATA change + // or if no packet of any type has been sent for some time + // send IDENTITY DATA about Avatar B to Avatar A + uint64_t lastBroadcastTime = nodeData->getLastBroadcastTime(otherNode->getUUID()); + if (lastBroadcastTime <= otherNodeData->getIdentityChangeTimestamp() || + startAvatarDataPacking > lastBroadcastTime + MIN_KEEP_ALIVE_PERIOD_FOR_OTHER_AVATAR) { identityBytesSent += sendIdentityPacket(otherNodeData, node); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 5ade0c448e..8319eb5249 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -110,6 +110,8 @@ const char LEFT_HAND_POINTING_FLAG = 1; const char RIGHT_HAND_POINTING_FLAG = 2; const char IS_FINGER_POINTING_FLAG = 4; +const qint64 AVATAR_UPDATE_TIMEOUT = 5 * USECS_PER_SECOND; + // AvatarData state flags - we store the details about the packet encoding in the first byte, // before the "header" structure const char AVATARDATA_FLAGS_MINIMUM = 0; @@ -599,10 +601,7 @@ public: } - bool shouldDie() const { - const qint64 AVATAR_SILENCE_THRESHOLD_USECS = 5 * USECS_PER_SECOND; - return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; - } + bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_UPDATE_TIMEOUT; } static const float OUT_OF_VIEW_PENALTY; From 63ea16f24b81a34e475e5b2e66f55b71348f1012 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 13 Apr 2017 13:37:18 -0700 Subject: [PATCH 4/8] avatar-mixer to send identity packets as 'reliable' --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index a4e9b608de..56c15f7793 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -69,7 +69,7 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { int bytesSent = 0; QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); - auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size()); + auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size(), true, true); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious bytesSent += individualData.size(); identityPacket->write(individualData); From 14b60165efb5c5f6126a0c19eeee8a9c24461ae5 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 13 Apr 2017 14:55:08 -0700 Subject: [PATCH 5/8] don't keep stale avatars alive --- .../src/avatars/AvatarMixerSlave.cpp | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 56c15f7793..ee3282f82b 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -67,15 +67,13 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { - int bytesSent = 0; QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); - auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size(), true, true); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious - bytesSent += individualData.size(); - identityPacket->write(individualData); - DependencyManager::get()->sendPacket(std::move(identityPacket), *destinationNode); + auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); + identityPackets->write(individualData); + DependencyManager::get()->sendPacketList(std::move(identityPackets), *destinationNode); _stats.numIdentityPackets++; - return bytesSent; + return individualData.size(); } static const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45; @@ -175,7 +173,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { } }); - const uint64_t MIN_KEEP_ALIVE_PERIOD_FOR_OTHER_AVATAR = (AVATAR_UPDATE_TIMEOUT - 1) * USECS_PER_SECOND; AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer(); ViewFrustum cameraView = nodeData->getViewFrustom(); std::priority_queue sortedAvatars; @@ -263,17 +260,11 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // have 15 (45hz-30hz) duplicate frames. In this case, the stat // avg_other_av_skips_per_second does report 15. // - // make sure we haven't already sent this data from this sender to that receiver + // make sure we haven't already sent this data from this sender to this receiver // or that somehow we haven't sent if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { - // don't ignore this avatar if we haven't sent any update for a long while - uint64_t lastBroadcastTime = nodeData->getLastBroadcastTime(avatarNode->getUUID()); - const AvatarMixerClientData* otherNodeData = reinterpret_cast(avatarNode->getLinkedData()); - if (lastBroadcastTime > otherNodeData->getIdentityChangeTimestamp() && - lastBroadcastTime > startIgnoreCalculation - MIN_KEEP_ALIVE_PERIOD_FOR_OTHER_AVATAR) { - ++numAvatarsHeldBack; - shouldIgnore = true; - } + ++numAvatarsHeldBack; + shouldIgnore = true; } else if (lastSeqFromSender - lastSeqToReceiver > 1) { // this is a skip - we still send the packet but capture the presence of the skip so we see it happening ++numAvatarsWithSkippedFrames; @@ -312,9 +303,10 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // the time that Avatar B flagged an IDENTITY DATA change // or if no packet of any type has been sent for some time // send IDENTITY DATA about Avatar B to Avatar A + const uint64_t AVATAR_UPDATE_STALE = AVATAR_UPDATE_TIMEOUT - USECS_PER_SECOND; uint64_t lastBroadcastTime = nodeData->getLastBroadcastTime(otherNode->getUUID()); if (lastBroadcastTime <= otherNodeData->getIdentityChangeTimestamp() || - startAvatarDataPacking > lastBroadcastTime + MIN_KEEP_ALIVE_PERIOD_FOR_OTHER_AVATAR) { + startAvatarDataPacking > lastBroadcastTime + AVATAR_UPDATE_STALE) { identityBytesSent += sendIdentityPacket(otherNodeData, node); } From 5bdecfbfa0b59cf1406c73aa127d768d3799e6d9 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 13 Apr 2017 16:00:11 -0700 Subject: [PATCH 6/8] send keep-alive packets but don't resend identity --- .../src/avatars/AvatarMixerSlave.cpp | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index ee3282f82b..6e3dd150a4 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -263,8 +263,16 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // make sure we haven't already sent this data from this sender to this receiver // or that somehow we haven't sent if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { - ++numAvatarsHeldBack; - shouldIgnore = true; + // don't ignore this avatar if we haven't sent any update for a long while + // in an effort to prevent other interfaces from deleting a stale avatar instance + uint64_t lastBroadcastTime = nodeData->getLastBroadcastTime(avatarNode->getUUID()); + const AvatarMixerClientData* otherNodeData = reinterpret_cast(avatarNode->getLinkedData()); + const uint64_t AVATAR_UPDATE_STALE = AVATAR_UPDATE_TIMEOUT - USECS_PER_SECOND; + if (lastBroadcastTime > otherNodeData->getIdentityChangeTimestamp() && + lastBroadcastTime + AVATAR_UPDATE_STALE > startIgnoreCalculation) { + ++numAvatarsHeldBack; + shouldIgnore = true; + } } else if (lastSeqFromSender - lastSeqToReceiver > 1) { // this is a skip - we still send the packet but capture the presence of the skip so we see it happening ++numAvatarsWithSkippedFrames; @@ -300,13 +308,8 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO - // the time that Avatar B flagged an IDENTITY DATA change - // or if no packet of any type has been sent for some time - // send IDENTITY DATA about Avatar B to Avatar A - const uint64_t AVATAR_UPDATE_STALE = AVATAR_UPDATE_TIMEOUT - USECS_PER_SECOND; - uint64_t lastBroadcastTime = nodeData->getLastBroadcastTime(otherNode->getUUID()); - if (lastBroadcastTime <= otherNodeData->getIdentityChangeTimestamp() || - startAvatarDataPacking > lastBroadcastTime + AVATAR_UPDATE_STALE) { + // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. + if (nodeData->getLastBroadcastTime(otherNode->getUUID()) <= otherNodeData->getIdentityChangeTimestamp()) { identityBytesSent += sendIdentityPacket(otherNodeData, node); } From af5a352f0289d0072c0e64ebaca64c325b44620b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 14 Apr 2017 08:10:31 -0700 Subject: [PATCH 7/8] turn on handControllerGrab debug prints --- scripts/system/controllers/handControllerGrab.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index d1c00a9d81..88d5229f1a 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -27,8 +27,8 @@ Script.include("/~/system/libraries/controllers.js"); // add lines where the hand ray picking is happening // -var WANT_DEBUG = false; -var WANT_DEBUG_STATE = false; +var WANT_DEBUG = true; +var WANT_DEBUG_STATE = true; var WANT_DEBUG_SEARCH_NAME = null; var FORCE_IGNORE_IK = false; From 7b48feca662b6f43bf2a641e5ab731c203c4d8ad Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 14 Apr 2017 11:53:11 -0700 Subject: [PATCH 8/8] address code review comments --- libraries/recording/src/recording/ClipCache.cpp | 3 ++- .../script-engine/src/RecordingScriptingInterface.cpp | 3 ++- .../script-engine/src/RecordingScriptingInterface.h | 1 - script-archive/acScripts/PlayRecordingOnAC.js | 2 ++ script-archive/acScripts/triggeredRecordingOnAC.js | 2 ++ script-archive/tests/playbackAcTest.js | 10 +++++++--- script-archive/vrShop/cash/shopCashierAC.js | 2 ++ script-archive/vrShop/review/shopReviewerAC.js | 2 +- scripts/developer/utilities/record/recorder.js | 2 ++ 9 files changed, 20 insertions(+), 7 deletions(-) diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp index 0665608bad..5c55c6bb1c 100644 --- a/libraries/recording/src/recording/ClipCache.cpp +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -10,6 +10,7 @@ #include "ClipCache.h" #include "impl/PointerClip.h" +#include "Logging.h" using namespace recording; NetworkClipLoader::NetworkClipLoader(const QUrl& url) : @@ -45,7 +46,7 @@ NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { } QSharedPointer ClipCache::createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) { - qDebug() << "Loading recording at" << url; + qCDebug(recordingLog) << "Loading recording at" << url; return QSharedPointer(new NetworkClipLoader(url), &Resource::deleter); } diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index e76a3859f0..36de1c1ef7 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -61,7 +61,8 @@ void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue auto weakClipLoader = clipLoader.toWeakRef(); // when clip loaded, call the callback with the URL and success boolean - connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, this, [this, weakClipLoader, url, callback]() mutable { + connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, this, + [this, weakClipLoader, url, callback]() mutable { if (auto clipLoader = weakClipLoader.toStrongRef()) { qCDebug(scriptengine) << "Loaded recording from" << url; diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index f6d335b17d..a9fdf1deb4 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -31,7 +31,6 @@ public: void setScriptEngine(QScriptEngine* scriptEngine) { _scriptEngine = scriptEngine; } public slots: - void loadRecording(const QString& url, QScriptValue callback = QScriptValue()); void startPlaying(); diff --git a/script-archive/acScripts/PlayRecordingOnAC.js b/script-archive/acScripts/PlayRecordingOnAC.js index 015f247e4a..5979913d23 100644 --- a/script-archive/acScripts/PlayRecordingOnAC.js +++ b/script-archive/acScripts/PlayRecordingOnAC.js @@ -23,6 +23,8 @@ Agent.isAvatar = true; Recording.loadRecording(recordingFile, function(success) { if (success) { Script.update.connect(update); + } else { + print("Failed to load recording from " + recordingFile); } }); diff --git a/script-archive/acScripts/triggeredRecordingOnAC.js b/script-archive/acScripts/triggeredRecordingOnAC.js index c3a87bc44e..964e82321f 100644 --- a/script-archive/acScripts/triggeredRecordingOnAC.js +++ b/script-archive/acScripts/triggeredRecordingOnAC.js @@ -47,6 +47,8 @@ var playRecording = function() { Recording.startPlaying(); isPlaying = true; isPlayable = false; // Set this true again after the cooldown period + } else { + print("Failed to load recording from " + CLIP_URL); } }); }; diff --git a/script-archive/tests/playbackAcTest.js b/script-archive/tests/playbackAcTest.js index 51ff1bdf02..5439f6f474 100644 --- a/script-archive/tests/playbackAcTest.js +++ b/script-archive/tests/playbackAcTest.js @@ -10,9 +10,13 @@ Agent.isAvatar = true; Script.setTimeout(function () { Avatar.position = origin; - Recording.loadRecording("d:/hifi.rec", function(success){ - Recording.setPlayerLoop(true); - Recording.startPlaying(); + Recording.loadRecording("d:/hifi.rec", function(success) { + if (success) { + Recording.setPlayerLoop(true); + Recording.startPlaying(); + } else { + print("Failed to load recording"); + } }); }, millisecondsToWaitBeforeStarting); diff --git a/script-archive/vrShop/cash/shopCashierAC.js b/script-archive/vrShop/cash/shopCashierAC.js index 91b45484fd..5236fa1218 100644 --- a/script-archive/vrShop/cash/shopCashierAC.js +++ b/script-archive/vrShop/cash/shopCashierAC.js @@ -60,6 +60,8 @@ function update(deltaTime) { Recording.setPlayerUseHeadModel(false); Recording.setPlayerUseSkeletonModel(useAvatarModel); Agent.isAvatar = true; + } else { + print("Failed to load recording from " + clip_url); } }); } diff --git a/script-archive/vrShop/review/shopReviewerAC.js b/script-archive/vrShop/review/shopReviewerAC.js index c39aaa2f4a..47b08e0a85 100644 --- a/script-archive/vrShop/review/shopReviewerAC.js +++ b/script-archive/vrShop/review/shopReviewerAC.js @@ -49,7 +49,7 @@ function getAction(channel, message, senderID) { case SHOW: print("Show"); - Recording.loadRecording(clip_url, function(success){ + Recording.loadRecording(clip_url, function(success) { if (success) { Agent.isAvatar = true; Recording.setPlayerTime(0.0); diff --git a/scripts/developer/utilities/record/recorder.js b/scripts/developer/utilities/record/recorder.js index b335274372..bda4edc125 100644 --- a/scripts/developer/utilities/record/recorder.js +++ b/scripts/developer/utilities/record/recorder.js @@ -284,6 +284,8 @@ function mousePressEvent(event) { Recording.loadRecording(recordingFile, function(success) { if (success) { setDefaultPlayerOptions(); + } else { + print("Failed to load recording from " + recordingFile); } }); }