mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 13:49:12 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into avatarExporterBlockExportFromConfigMaster
This commit is contained in:
commit
8db6abb58d
73 changed files with 609 additions and 466 deletions
|
@ -52,6 +52,8 @@
|
||||||
#include <WebSocketServerClass.h>
|
#include <WebSocketServerClass.h>
|
||||||
#include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
|
#include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
|
||||||
|
|
||||||
|
#include <hfm/ModelFormatRegistry.h>
|
||||||
|
|
||||||
#include "entities/AssignmentParentFinder.h"
|
#include "entities/AssignmentParentFinder.h"
|
||||||
#include "AssignmentDynamicFactory.h"
|
#include "AssignmentDynamicFactory.h"
|
||||||
#include "RecordingScriptingInterface.h"
|
#include "RecordingScriptingInterface.h"
|
||||||
|
@ -99,6 +101,9 @@ Agent::Agent(ReceivedMessage& message) :
|
||||||
DependencyManager::set<RecordingScriptingInterface>();
|
DependencyManager::set<RecordingScriptingInterface>();
|
||||||
DependencyManager::set<UsersScriptingInterface>();
|
DependencyManager::set<UsersScriptingInterface>();
|
||||||
|
|
||||||
|
DependencyManager::set<ModelFormatRegistry>();
|
||||||
|
DependencyManager::set<ModelCache>();
|
||||||
|
|
||||||
// Needed to ensure the creation of the DebugDraw instance on the main thread
|
// Needed to ensure the creation of the DebugDraw instance on the main thread
|
||||||
DebugDraw::getInstance();
|
DebugDraw::getInstance();
|
||||||
|
|
||||||
|
@ -819,6 +824,9 @@ void Agent::aboutToFinish() {
|
||||||
|
|
||||||
DependencyManager::get<ResourceManager>()->cleanup();
|
DependencyManager::get<ResourceManager>()->cleanup();
|
||||||
|
|
||||||
|
DependencyManager::destroy<ModelFormatRegistry>();
|
||||||
|
DependencyManager::destroy<ModelCache>();
|
||||||
|
|
||||||
DependencyManager::destroy<PluginManager>();
|
DependencyManager::destroy<PluginManager>();
|
||||||
|
|
||||||
// cleanup the AudioInjectorManager (and any still running injectors)
|
// cleanup the AudioInjectorManager (and any still running injectors)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
macro(TARGET_PYTHON)
|
macro(TARGET_PYTHON)
|
||||||
if (NOT HIFI_PYTHON_EXEC)
|
if (NOT HIFI_PYTHON_EXEC)
|
||||||
# Find the python interpreter
|
# Find the python interpreter
|
||||||
if (CAME_VERSION VERSION_LESS 3.12)
|
if (CMAKE_VERSION VERSION_LESS 3.12)
|
||||||
# this logic is deprecated in CMake after 3.12
|
# this logic is deprecated in CMake after 3.12
|
||||||
# FIXME eventually we should make 3.12 the min cmake verion and just use the Python3 find_package path
|
# FIXME eventually we should make 3.12 the min cmake verion and just use the Python3 find_package path
|
||||||
set(Python_ADDITIONAL_VERSIONS 3)
|
set(Python_ADDITIONAL_VERSIONS 3)
|
||||||
|
|
|
@ -379,9 +379,9 @@ Item {
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// with the link.
|
// with the link.
|
||||||
if (completeProfileBody.withOculus) {
|
if (completeProfileBody.withOculus) {
|
||||||
termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
|
termsText.text = qsTr("By signing up, you agree to <a href='https://www.highfidelity.com/termsofservice'>High Fidelity's Terms of Service</a>")
|
||||||
} else {
|
} else {
|
||||||
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
|
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://www.highfidelity.com/termsofservice'>High Fidelity's Terms of Service</a>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -395,7 +395,7 @@ Item {
|
||||||
text: signUpBody.termsContainerText
|
text: signUpBody.termsContainerText
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// with the link.
|
// with the link.
|
||||||
termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
|
termsText.text = qsTr("By signing up, you agree to <a href='https://www.highfidelity.com/termsofservice'>High Fidelity's Terms of Service</a>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -218,7 +218,7 @@ Item {
|
||||||
text: usernameCollisionBody.termsContainerText
|
text: usernameCollisionBody.termsContainerText
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// with the link.
|
// with the link.
|
||||||
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
|
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://www.highfidelity.com/termsofservice'>High Fidelity's Terms of Service</a>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -232,6 +232,10 @@ Item {
|
||||||
text: "Audio Codec: " + root.audioCodec + " Noise Gate: " +
|
text: "Audio Codec: " + root.audioCodec + " Noise Gate: " +
|
||||||
root.audioNoiseGate;
|
root.audioNoiseGate;
|
||||||
}
|
}
|
||||||
|
StatText {
|
||||||
|
visible: root.expanded;
|
||||||
|
text: "Injectors (Local/NonLocal): " + root.audioInjectors.x + "/" + root.audioInjectors.y;
|
||||||
|
}
|
||||||
StatText {
|
StatText {
|
||||||
visible: root.expanded;
|
visible: root.expanded;
|
||||||
text: "Entity Servers In: " + root.entityPacketsInKbps + " kbps";
|
text: "Entity Servers In: " + root.entityPacketsInKbps + " kbps";
|
||||||
|
|
|
@ -28,7 +28,7 @@ TabletModalWindow {
|
||||||
id: mouse;
|
id: mouse;
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
function click(button) {
|
function click(button) {
|
||||||
clickedButton = button;
|
clickedButton = button;
|
||||||
selected(button);
|
selected(button);
|
||||||
|
|
|
@ -117,7 +117,6 @@ Rectangle {
|
||||||
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
|
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
|
||||||
loader.item.gotoPreviousApp = true;
|
loader.item.gotoPreviousApp = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
screenChanged("Web", url)
|
screenChanged("Web", url)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1985,6 +1985,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
return nullptr;
|
return nullptr;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
EntityTree::setEmitScriptEventOperator([this](const QUuid& id, const QVariant& message) {
|
||||||
|
auto entities = getEntities();
|
||||||
|
if (auto entity = entities->renderableForEntityId(id)) {
|
||||||
|
entity->emitScriptEvent(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
EntityTree::setTextSizeOperator([this](const QUuid& id, const QString& text) {
|
EntityTree::setTextSizeOperator([this](const QUuid& id, const QString& text) {
|
||||||
auto entities = getEntities();
|
auto entities = getEntities();
|
||||||
if (auto entity = entities->renderableForEntityId(id)) {
|
if (auto entity = entities->renderableForEntityId(id)) {
|
||||||
|
@ -2342,6 +2349,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
return viewFrustum.getPosition();
|
return viewFrustum.getPosition();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
DependencyManager::get<UsersScriptingInterface>()->setKickConfirmationOperator([this] (const QUuid& nodeID) { userKickConfirmation(nodeID); });
|
||||||
|
|
||||||
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
|
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
|
||||||
bool isTablet = url == TabletScriptingInterface::QML;
|
bool isTablet = url == TabletScriptingInterface::QML;
|
||||||
if (htmlContent) {
|
if (htmlContent) {
|
||||||
|
@ -2704,9 +2713,7 @@ void Application::cleanupBeforeQuit() {
|
||||||
|
|
||||||
DependencyManager::destroy<OffscreenQmlSurfaceCache>();
|
DependencyManager::destroy<OffscreenQmlSurfaceCache>();
|
||||||
|
|
||||||
if (_snapshotSoundInjector != nullptr) {
|
_snapshotSoundInjector = nullptr;
|
||||||
_snapshotSoundInjector->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// destroy Audio so it and its threads have a chance to go down safely
|
// destroy Audio so it and its threads have a chance to go down safely
|
||||||
// this must happen after QML, as there are unexplained audio crashes originating in qtwebengine
|
// this must happen after QML, as there are unexplained audio crashes originating in qtwebengine
|
||||||
|
@ -3290,6 +3297,40 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::userKickConfirmation(const QUuid& nodeID) {
|
||||||
|
auto avatarHashMap = DependencyManager::get<AvatarHashMap>();
|
||||||
|
auto avatar = avatarHashMap->getAvatarBySessionID(nodeID);
|
||||||
|
|
||||||
|
QString userName;
|
||||||
|
|
||||||
|
if (avatar) {
|
||||||
|
userName = avatar->getSessionDisplayName();
|
||||||
|
} else {
|
||||||
|
userName = nodeID.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString kickMessage = "Do you wish to kick " + userName + " from your domain";
|
||||||
|
ModalDialogListener* dlg = OffscreenUi::asyncQuestion("Kick User", kickMessage,
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
|
||||||
|
if (dlg->getDialogItem()) {
|
||||||
|
|
||||||
|
QObject::connect(dlg, &ModalDialogListener::response, this, [=] (QVariant answer) {
|
||||||
|
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||||
|
|
||||||
|
bool yes = (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes);
|
||||||
|
// ask the NodeList to kick the user with the given session ID
|
||||||
|
|
||||||
|
if (yes) {
|
||||||
|
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
|
||||||
|
}
|
||||||
|
|
||||||
|
DependencyManager::get<UsersScriptingInterface>()->setWaitForKickResponse(false);
|
||||||
|
});
|
||||||
|
DependencyManager::get<UsersScriptingInterface>()->setWaitForKickResponse(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties) {
|
void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties) {
|
||||||
surfaceContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
|
surfaceContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
|
||||||
surfaceContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
surfaceContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
||||||
|
@ -4228,10 +4269,9 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
||||||
Setting::Handle<bool> notificationSoundSnapshot{ MenuOption::NotificationSoundsSnapshot, true };
|
Setting::Handle<bool> notificationSoundSnapshot{ MenuOption::NotificationSoundsSnapshot, true };
|
||||||
if (notificationSounds.get() && notificationSoundSnapshot.get()) {
|
if (notificationSounds.get() && notificationSoundSnapshot.get()) {
|
||||||
if (_snapshotSoundInjector) {
|
if (_snapshotSoundInjector) {
|
||||||
_snapshotSoundInjector->setOptions(options);
|
DependencyManager::get<AudioInjectorManager>()->setOptionsAndRestart(_snapshotSoundInjector, options);
|
||||||
_snapshotSoundInjector->restart();
|
|
||||||
} else {
|
} else {
|
||||||
_snapshotSoundInjector = AudioInjector::playSound(_snapshotSound, options);
|
_snapshotSoundInjector = DependencyManager::get<AudioInjectorManager>()->playSound(_snapshotSound, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
takeSnapshot(true);
|
takeSnapshot(true);
|
||||||
|
|
|
@ -593,6 +593,7 @@ private:
|
||||||
void toggleTabletUI(bool shouldOpen = false) const;
|
void toggleTabletUI(bool shouldOpen = false) const;
|
||||||
|
|
||||||
static void setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties);
|
static void setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties);
|
||||||
|
void userKickConfirmation(const QUuid& nodeID);
|
||||||
|
|
||||||
MainWindow* _window;
|
MainWindow* _window;
|
||||||
QElapsedTimer& _sessionRunTimer;
|
QElapsedTimer& _sessionRunTimer;
|
||||||
|
|
|
@ -149,6 +149,9 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) {
|
||||||
emit bookmarkDeleted(bookmarkName);
|
emit bookmarkDeleted(bookmarkName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarBookmarks::deleteBookmark() {
|
||||||
|
}
|
||||||
|
|
||||||
void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
|
void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
|
||||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
auto currentAvatarEntities = myAvatar->getAvatarEntityData();
|
auto currentAvatarEntities = myAvatar->getAvatarEntityData();
|
||||||
|
|
|
@ -76,6 +76,9 @@ protected:
|
||||||
void readFromFile() override;
|
void readFromFile() override;
|
||||||
QVariantMap getAvatarDataToBookmark();
|
QVariantMap getAvatarDataToBookmark();
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void deleteBookmark() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";
|
const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";
|
||||||
const QString ENTRY_AVATAR_URL = "avatarUrl";
|
const QString ENTRY_AVATAR_URL = "avatarUrl";
|
||||||
|
|
|
@ -51,13 +51,10 @@ protected:
|
||||||
bool _isMenuSorted;
|
bool _isMenuSorted;
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
/**jsdoc
|
|
||||||
* @function AvatarBookmarks.deleteBookmark
|
|
||||||
*/
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @function LocationBookmarks.deleteBookmark
|
* @function LocationBookmarks.deleteBookmark
|
||||||
*/
|
*/
|
||||||
void deleteBookmark();
|
virtual void deleteBookmark();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool sortOrder(QAction* a, QAction* b);
|
static bool sortOrder(QAction* a, QAction* b);
|
||||||
|
|
|
@ -629,8 +629,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
|
||||||
// but most avatars are roughly the same size, so let's not be so fancy yet.
|
// but most avatars are roughly the same size, so let's not be so fancy yet.
|
||||||
const float AVATAR_STRETCH_FACTOR = 1.0f;
|
const float AVATAR_STRETCH_FACTOR = 1.0f;
|
||||||
|
|
||||||
_collisionInjectors.remove_if(
|
_collisionInjectors.remove_if([](const AudioInjectorPointer& injector) { return !injector; });
|
||||||
[](const AudioInjectorPointer& injector) { return !injector || injector->isFinished(); });
|
|
||||||
|
|
||||||
static const int MAX_INJECTOR_COUNT = 3;
|
static const int MAX_INJECTOR_COUNT = 3;
|
||||||
if (_collisionInjectors.size() < MAX_INJECTOR_COUNT) {
|
if (_collisionInjectors.size() < MAX_INJECTOR_COUNT) {
|
||||||
|
@ -640,7 +639,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
|
||||||
options.volume = energyFactorOfFull;
|
options.volume = energyFactorOfFull;
|
||||||
options.pitch = 1.0f / AVATAR_STRETCH_FACTOR;
|
options.pitch = 1.0f / AVATAR_STRETCH_FACTOR;
|
||||||
|
|
||||||
auto injector = AudioInjector::playSoundAndDelete(collisionSound, options);
|
auto injector = DependencyManager::get<AudioInjectorManager>()->playSound(collisionSound, options, true);
|
||||||
_collisionInjectors.emplace_back(injector);
|
_collisionInjectors.emplace_back(injector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
#include <SimpleMovingAverage.h>
|
#include <SimpleMovingAverage.h>
|
||||||
#include <shared/RateCounter.h>
|
#include <shared/RateCounter.h>
|
||||||
#include <avatars-renderer/ScriptAvatar.h>
|
#include <avatars-renderer/ScriptAvatar.h>
|
||||||
#include <AudioInjector.h>
|
#include <AudioInjectorManager.h>
|
||||||
#include <workload/Space.h>
|
#include <workload/Space.h>
|
||||||
#include <EntitySimulation.h> // for SetOfEntities
|
#include <EntitySimulation.h> // for SetOfEntities
|
||||||
|
|
||||||
|
@ -239,7 +239,7 @@ private:
|
||||||
std::shared_ptr<MyAvatar> _myAvatar;
|
std::shared_ptr<MyAvatar> _myAvatar;
|
||||||
quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate.
|
quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate.
|
||||||
|
|
||||||
std::list<AudioInjectorPointer> _collisionInjectors;
|
std::list<QWeakPointer<AudioInjector>> _collisionInjectors;
|
||||||
|
|
||||||
RateCounter<> _myAvatarSendRate;
|
RateCounter<> _myAvatarSendRate;
|
||||||
int _numAvatarsUpdated { 0 };
|
int _numAvatarsUpdated { 0 };
|
||||||
|
|
|
@ -1570,7 +1570,7 @@ void MyAvatar::handleChangedAvatarEntityData() {
|
||||||
entityTree->withWriteLock([&] {
|
entityTree->withWriteLock([&] {
|
||||||
EntityItemPointer entity = entityTree->addEntity(id, properties);
|
EntityItemPointer entity = entityTree->addEntity(id, properties);
|
||||||
if (entity) {
|
if (entity) {
|
||||||
packetSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, properties);
|
packetSender->queueEditAvatarEntityMessage(entityTree, id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3451,10 +3451,15 @@ float MyAvatar::getGravity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::setSessionUUID(const QUuid& sessionUUID) {
|
void MyAvatar::setSessionUUID(const QUuid& sessionUUID) {
|
||||||
QUuid oldID = getSessionUUID();
|
QUuid oldSessionID = getSessionUUID();
|
||||||
Avatar::setSessionUUID(sessionUUID);
|
Avatar::setSessionUUID(sessionUUID);
|
||||||
QUuid id = getSessionUUID();
|
QUuid newSessionID = getSessionUUID();
|
||||||
if (id != oldID) {
|
if (DependencyManager::get<NodeList>()->getSessionUUID().isNull()) {
|
||||||
|
// we don't actually have a connection to a domain right now
|
||||||
|
// so there is no need to queue AvatarEntity messages --> bail early
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newSessionID != oldSessionID) {
|
||||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||||
if (entityTree) {
|
if (entityTree) {
|
||||||
|
@ -3462,15 +3467,20 @@ void MyAvatar::setSessionUUID(const QUuid& sessionUUID) {
|
||||||
_avatarEntitiesLock.withReadLock([&] {
|
_avatarEntitiesLock.withReadLock([&] {
|
||||||
avatarEntityIDs = _packedAvatarEntityData.keys();
|
avatarEntityIDs = _packedAvatarEntityData.keys();
|
||||||
});
|
});
|
||||||
|
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
|
||||||
entityTree->withWriteLock([&] {
|
entityTree->withWriteLock([&] {
|
||||||
for (const auto& entityID : avatarEntityIDs) {
|
for (const auto& entityID : avatarEntityIDs) {
|
||||||
auto entity = entityTree->findEntityByID(entityID);
|
auto entity = entityTree->findEntityByID(entityID);
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
entity->setOwningAvatarID(id);
|
entity->setOwningAvatarID(newSessionID);
|
||||||
if (entity->getParentID() == oldID) {
|
// NOTE: each attached AvatarEntity should already have the correct updated parentID
|
||||||
entity->setParentID(id);
|
// via magic in SpatiallyNestable, but when an AvatarEntity IS parented to MyAvatar
|
||||||
|
// we need to update the "packedAvatarEntityData" we send to the avatar-mixer
|
||||||
|
// so that others will get the updated state.
|
||||||
|
if (entity->getParentID() == newSessionID) {
|
||||||
|
packetSender->queueEditAvatarEntityMessage(entityTree, entityID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -5523,14 +5533,14 @@ void MyAvatar::initFlowFromFST() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const {
|
void MyAvatar::sendPacket(const QUuid& entityID) const {
|
||||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||||
if (entityTree) {
|
if (entityTree) {
|
||||||
entityTree->withWriteLock([&] {
|
entityTree->withWriteLock([&] {
|
||||||
// force an update packet
|
// force an update packet
|
||||||
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
|
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
|
||||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityID, properties);
|
packetSender->queueEditAvatarEntityMessage(entityTree, entityID);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1122,6 +1122,7 @@ public:
|
||||||
float getUserEyeHeight() const;
|
float getUserEyeHeight() const;
|
||||||
|
|
||||||
virtual SpatialParentTree* getParentTree() const override;
|
virtual SpatialParentTree* getParentTree() const override;
|
||||||
|
virtual glm::vec3 scaleForChildren() const override { return glm::vec3(getSensorToWorldScale()); }
|
||||||
|
|
||||||
const QUuid& getSelfID() const { return AVATAR_SELF_ID; }
|
const QUuid& getSelfID() const { return AVATAR_SELF_ID; }
|
||||||
|
|
||||||
|
@ -1918,7 +1919,7 @@ private:
|
||||||
bool didTeleport();
|
bool didTeleport();
|
||||||
bool getIsAway() const { return _isAway; }
|
bool getIsAway() const { return _isAway; }
|
||||||
void setAway(bool value);
|
void setAway(bool value);
|
||||||
void sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const override;
|
void sendPacket(const QUuid& entityID) const override;
|
||||||
|
|
||||||
std::mutex _pinnedJointsMutex;
|
std::mutex _pinnedJointsMutex;
|
||||||
std::vector<int> _pinnedJoints;
|
std::vector<int> _pinnedJoints;
|
||||||
|
|
|
@ -66,7 +66,7 @@ void TTSScriptingInterface::updateLastSoundAudioInjector() {
|
||||||
if (_lastSoundAudioInjector) {
|
if (_lastSoundAudioInjector) {
|
||||||
AudioInjectorOptions options;
|
AudioInjectorOptions options;
|
||||||
options.position = DependencyManager::get<AvatarManager>()->getMyAvatarPosition();
|
options.position = DependencyManager::get<AvatarManager>()->getMyAvatarPosition();
|
||||||
_lastSoundAudioInjector->setOptions(options);
|
DependencyManager::get<AudioInjectorManager>()->setOptions(_lastSoundAudioInjector, options);
|
||||||
_lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS);
|
_lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,7 +143,7 @@ void TTSScriptingInterface::speakText(const QString& textToSpeak) {
|
||||||
options.position = DependencyManager::get<AvatarManager>()->getMyAvatarPosition();
|
options.position = DependencyManager::get<AvatarManager>()->getMyAvatarPosition();
|
||||||
|
|
||||||
if (_lastSoundAudioInjector) {
|
if (_lastSoundAudioInjector) {
|
||||||
_lastSoundAudioInjector->stop();
|
DependencyManager::get<AudioInjectorManager>()->stop(_lastSoundAudioInjector);
|
||||||
_lastSoundAudioInjectorUpdateTimer.stop();
|
_lastSoundAudioInjectorUpdateTimer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ void TTSScriptingInterface::speakText(const QString& textToSpeak) {
|
||||||
uint32_t numSamples = (uint32_t)_lastSoundByteArray.size() / sizeof(AudioData::AudioSample);
|
uint32_t numSamples = (uint32_t)_lastSoundByteArray.size() / sizeof(AudioData::AudioSample);
|
||||||
auto samples = reinterpret_cast<AudioData::AudioSample*>(_lastSoundByteArray.data());
|
auto samples = reinterpret_cast<AudioData::AudioSample*>(_lastSoundByteArray.data());
|
||||||
auto newAudioData = AudioData::make(numSamples, numChannels, samples);
|
auto newAudioData = AudioData::make(numSamples, numChannels, samples);
|
||||||
_lastSoundAudioInjector = AudioInjector::playSoundAndDelete(newAudioData, options);
|
_lastSoundAudioInjector = DependencyManager::get<AudioInjectorManager>()->playSound(newAudioData, options, true);
|
||||||
|
|
||||||
_lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS);
|
_lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS);
|
||||||
#else
|
#else
|
||||||
|
@ -161,7 +161,7 @@ void TTSScriptingInterface::speakText(const QString& textToSpeak) {
|
||||||
|
|
||||||
void TTSScriptingInterface::stopLastSpeech() {
|
void TTSScriptingInterface::stopLastSpeech() {
|
||||||
if (_lastSoundAudioInjector) {
|
if (_lastSoundAudioInjector) {
|
||||||
_lastSoundAudioInjector->stop();
|
DependencyManager::get<AudioInjectorManager>()->stop(_lastSoundAudioInjector);
|
||||||
_lastSoundAudioInjector = NULL;
|
_lastSoundAudioInjector = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,13 +199,3 @@ void TestScriptingInterface::setOtherAvatarsReplicaCount(int count) {
|
||||||
int TestScriptingInterface::getOtherAvatarsReplicaCount() {
|
int TestScriptingInterface::getOtherAvatarsReplicaCount() {
|
||||||
return qApp->getOtherAvatarsReplicaCount();
|
return qApp->getOtherAvatarsReplicaCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TestScriptingInterface::getOperatingSystemType() {
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
return "WINDOWS";
|
|
||||||
#elif defined Q_OS_MAC
|
|
||||||
return "MACOS";
|
|
||||||
#else
|
|
||||||
return "UNKNOWN";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
|
@ -163,13 +163,6 @@ public slots:
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE int getOtherAvatarsReplicaCount();
|
Q_INVOKABLE int getOtherAvatarsReplicaCount();
|
||||||
|
|
||||||
/**jsdoc
|
|
||||||
* Returns the Operating Sytem type
|
|
||||||
* @function Test.getOperatingSystemType
|
|
||||||
* @returns {string} "WINDOWS", "MACOS" or "UNKNOWN"
|
|
||||||
*/
|
|
||||||
QString getOperatingSystemType();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool waitForCondition(qint64 maxWaitMs, std::function<bool()> condition);
|
bool waitForCondition(qint64 maxWaitMs, std::function<bool()> condition);
|
||||||
QString _testResultsLocation;
|
QString _testResultsLocation;
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
#include <PathUtils.h>
|
#include <PathUtils.h>
|
||||||
#include <ResourceManager.h>
|
#include <ResourceManager.h>
|
||||||
#include <SoundCache.h>
|
#include <SoundCache.h>
|
||||||
#include <AudioInjector.h>
|
#include <AudioInjectorManager.h>
|
||||||
#include <RegisteredMetaTypes.h>
|
#include <RegisteredMetaTypes.h>
|
||||||
#include <ui/TabletScriptingInterface.h>
|
#include <ui/TabletScriptingInterface.h>
|
||||||
|
|
||||||
|
@ -537,7 +537,7 @@ void Keyboard::handleTriggerBegin(const QUuid& id, const PointerEvent& event) {
|
||||||
audioOptions.position = keyWorldPosition;
|
audioOptions.position = keyWorldPosition;
|
||||||
audioOptions.volume = 0.05f;
|
audioOptions.volume = 0.05f;
|
||||||
|
|
||||||
AudioInjector::playSoundAndDelete(_keySound, audioOptions);
|
DependencyManager::get<AudioInjectorManager>()->playSound(_keySound, audioOptions, true);
|
||||||
|
|
||||||
int scanCode = key.getScanCode(_capsEnabled);
|
int scanCode = key.getScanCode(_capsEnabled);
|
||||||
QString keyString = key.getKeyString(_capsEnabled);
|
QString keyString = key.getKeyString(_capsEnabled);
|
||||||
|
|
|
@ -19,9 +19,9 @@
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QUuid>
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
#include <Sound.h>
|
#include <Sound.h>
|
||||||
#include <AudioInjector.h>
|
|
||||||
#include <shared/ReadWriteLockable.h>
|
#include <shared/ReadWriteLockable.h>
|
||||||
#include <SettingHandle.h>
|
#include <SettingHandle.h>
|
||||||
|
|
||||||
|
|
|
@ -266,6 +266,11 @@ void Stats::updateStats(bool force) {
|
||||||
}
|
}
|
||||||
STAT_UPDATE(audioCodec, audioClient->getSelectedAudioFormat());
|
STAT_UPDATE(audioCodec, audioClient->getSelectedAudioFormat());
|
||||||
STAT_UPDATE(audioNoiseGate, audioClient->getNoiseGateOpen() ? "Open" : "Closed");
|
STAT_UPDATE(audioNoiseGate, audioClient->getNoiseGateOpen() ? "Open" : "Closed");
|
||||||
|
{
|
||||||
|
int localInjectors = audioClient->getNumLocalInjectors();
|
||||||
|
size_t nonLocalInjectors = DependencyManager::get<AudioInjectorManager>()->getNumInjectors();
|
||||||
|
STAT_UPDATE(audioInjectors, QVector2D(localInjectors, nonLocalInjectors));
|
||||||
|
}
|
||||||
|
|
||||||
STAT_UPDATE(entityPacketsInKbps, octreeServerCount ? totalEntityKbps / octreeServerCount : -1);
|
STAT_UPDATE(entityPacketsInKbps, octreeServerCount ? totalEntityKbps / octreeServerCount : -1);
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,7 @@ private: \
|
||||||
* @property {number} audioPacketLoss - <em>Read-only.</em>
|
* @property {number} audioPacketLoss - <em>Read-only.</em>
|
||||||
* @property {string} audioCodec - <em>Read-only.</em>
|
* @property {string} audioCodec - <em>Read-only.</em>
|
||||||
* @property {string} audioNoiseGate - <em>Read-only.</em>
|
* @property {string} audioNoiseGate - <em>Read-only.</em>
|
||||||
|
* @property {Vec2} audioInjectors - <em>Read-only.</em>
|
||||||
* @property {number} entityPacketsInKbps - <em>Read-only.</em>
|
* @property {number} entityPacketsInKbps - <em>Read-only.</em>
|
||||||
*
|
*
|
||||||
* @property {number} downloads - <em>Read-only.</em>
|
* @property {number} downloads - <em>Read-only.</em>
|
||||||
|
@ -243,6 +244,7 @@ class Stats : public QQuickItem {
|
||||||
STATS_PROPERTY(int, audioPacketLoss, 0)
|
STATS_PROPERTY(int, audioPacketLoss, 0)
|
||||||
STATS_PROPERTY(QString, audioCodec, QString())
|
STATS_PROPERTY(QString, audioCodec, QString())
|
||||||
STATS_PROPERTY(QString, audioNoiseGate, QString())
|
STATS_PROPERTY(QString, audioNoiseGate, QString())
|
||||||
|
STATS_PROPERTY(QVector2D, audioInjectors, QVector2D());
|
||||||
STATS_PROPERTY(int, entityPacketsInKbps, 0)
|
STATS_PROPERTY(int, entityPacketsInKbps, 0)
|
||||||
|
|
||||||
STATS_PROPERTY(int, downloads, 0)
|
STATS_PROPERTY(int, downloads, 0)
|
||||||
|
@ -692,6 +694,13 @@ signals:
|
||||||
*/
|
*/
|
||||||
void audioNoiseGateChanged();
|
void audioNoiseGateChanged();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Triggered when the value of the <code>audioInjectors</code> property changes.
|
||||||
|
* @function Stats.audioInjectorsChanged
|
||||||
|
* @returns {Signal}
|
||||||
|
*/
|
||||||
|
void audioInjectorsChanged();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Triggered when the value of the <code>entityPacketsInKbps</code> property changes.
|
* Triggered when the value of the <code>entityPacketsInKbps</code> property changes.
|
||||||
* @function Stats.entityPacketsInKbpsChanged
|
* @function Stats.entityPacketsInKbpsChanged
|
||||||
|
|
|
@ -1354,26 +1354,28 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
||||||
|
|
||||||
for (const AudioInjectorPointer& injector : _activeLocalAudioInjectors) {
|
for (const AudioInjectorPointer& injector : _activeLocalAudioInjectors) {
|
||||||
// the lock guarantees that injectorBuffer, if found, is invariant
|
// the lock guarantees that injectorBuffer, if found, is invariant
|
||||||
AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
|
auto injectorBuffer = injector->getLocalBuffer();
|
||||||
if (injectorBuffer) {
|
if (injectorBuffer) {
|
||||||
|
|
||||||
|
auto options = injector->getOptions();
|
||||||
|
|
||||||
static const int HRTF_DATASET_INDEX = 1;
|
static const int HRTF_DATASET_INDEX = 1;
|
||||||
|
|
||||||
int numChannels = injector->isAmbisonic() ? AudioConstants::AMBISONIC : (injector->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO);
|
int numChannels = options.ambisonic ? AudioConstants::AMBISONIC : (options.stereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||||
size_t bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
size_t bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
||||||
|
|
||||||
// get one frame from the injector
|
// get one frame from the injector
|
||||||
memset(_localScratchBuffer, 0, bytesToRead);
|
memset(_localScratchBuffer, 0, bytesToRead);
|
||||||
if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
|
if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
|
||||||
|
|
||||||
float gain = injector->getVolume();
|
float gain = options.volume;
|
||||||
|
|
||||||
if (injector->isAmbisonic()) {
|
if (options.ambisonic) {
|
||||||
|
|
||||||
if (injector->isPositionSet()) {
|
if (options.positionSet) {
|
||||||
|
|
||||||
// distance attenuation
|
// distance attenuation
|
||||||
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
|
glm::vec3 relativePosition = options.position - _positionGetter();
|
||||||
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
||||||
gain = gainForSource(distance, gain);
|
gain = gainForSource(distance, gain);
|
||||||
}
|
}
|
||||||
|
@ -1382,7 +1384,7 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
||||||
// Calculate the soundfield orientation relative to the listener.
|
// Calculate the soundfield orientation relative to the listener.
|
||||||
// Injector orientation can be used to align a recording to our world coordinates.
|
// Injector orientation can be used to align a recording to our world coordinates.
|
||||||
//
|
//
|
||||||
glm::quat relativeOrientation = injector->getOrientation() * glm::inverse(_orientationGetter());
|
glm::quat relativeOrientation = options.orientation * glm::inverse(_orientationGetter());
|
||||||
|
|
||||||
// convert from Y-up (OpenGL) to Z-up (Ambisonic) coordinate system
|
// convert from Y-up (OpenGL) to Z-up (Ambisonic) coordinate system
|
||||||
float qw = relativeOrientation.w;
|
float qw = relativeOrientation.w;
|
||||||
|
@ -1394,12 +1396,12 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
||||||
injector->getLocalFOA().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
|
injector->getLocalFOA().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
|
||||||
qw, qx, qy, qz, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
qw, qx, qy, qz, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||||
|
|
||||||
} else if (injector->isStereo()) {
|
} else if (options.stereo) {
|
||||||
|
|
||||||
if (injector->isPositionSet()) {
|
if (options.positionSet) {
|
||||||
|
|
||||||
// distance attenuation
|
// distance attenuation
|
||||||
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
|
glm::vec3 relativePosition = options.position - _positionGetter();
|
||||||
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
||||||
gain = gainForSource(distance, gain);
|
gain = gainForSource(distance, gain);
|
||||||
}
|
}
|
||||||
|
@ -1412,10 +1414,10 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
||||||
|
|
||||||
} else { // injector is mono
|
} else { // injector is mono
|
||||||
|
|
||||||
if (injector->isPositionSet()) {
|
if (options.positionSet) {
|
||||||
|
|
||||||
// distance attenuation
|
// distance attenuation
|
||||||
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
|
glm::vec3 relativePosition = options.position - _positionGetter();
|
||||||
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
||||||
gain = gainForSource(distance, gain);
|
gain = gainForSource(distance, gain);
|
||||||
|
|
||||||
|
@ -1437,21 +1439,21 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
qCDebug(audioclient) << "injector has no more data, marking finished for removal";
|
//qCDebug(audioclient) << "injector has no more data, marking finished for removal";
|
||||||
injector->finishLocalInjection();
|
injector->finishLocalInjection();
|
||||||
injectorsToRemove.append(injector);
|
injectorsToRemove.append(injector);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
qCDebug(audioclient) << "injector has no local buffer, marking as finished for removal";
|
//qCDebug(audioclient) << "injector has no local buffer, marking as finished for removal";
|
||||||
injector->finishLocalInjection();
|
injector->finishLocalInjection();
|
||||||
injectorsToRemove.append(injector);
|
injectorsToRemove.append(injector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const AudioInjectorPointer& injector : injectorsToRemove) {
|
for (const AudioInjectorPointer& injector : injectorsToRemove) {
|
||||||
qCDebug(audioclient) << "removing injector";
|
//qCDebug(audioclient) << "removing injector";
|
||||||
_activeLocalAudioInjectors.removeOne(injector);
|
_activeLocalAudioInjectors.removeOne(injector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1571,15 +1573,13 @@ bool AudioClient::setIsStereoInput(bool isStereoInput) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioClient::outputLocalInjector(const AudioInjectorPointer& injector) {
|
bool AudioClient::outputLocalInjector(const AudioInjectorPointer& injector) {
|
||||||
AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
|
auto injectorBuffer = injector->getLocalBuffer();
|
||||||
if (injectorBuffer) {
|
if (injectorBuffer) {
|
||||||
// local injectors are on the AudioInjectorsThread, so we must guard access
|
// local injectors are on the AudioInjectorsThread, so we must guard access
|
||||||
Lock lock(_injectorsMutex);
|
Lock lock(_injectorsMutex);
|
||||||
if (!_activeLocalAudioInjectors.contains(injector)) {
|
if (!_activeLocalAudioInjectors.contains(injector)) {
|
||||||
qCDebug(audioclient) << "adding new injector";
|
//qCDebug(audioclient) << "adding new injector";
|
||||||
_activeLocalAudioInjectors.append(injector);
|
_activeLocalAudioInjectors.append(injector);
|
||||||
// move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop())
|
|
||||||
injectorBuffer->setParent(nullptr);
|
|
||||||
|
|
||||||
// update the flag
|
// update the flag
|
||||||
_localInjectorsAvailable.exchange(true, std::memory_order_release);
|
_localInjectorsAvailable.exchange(true, std::memory_order_release);
|
||||||
|
@ -1595,6 +1595,11 @@ bool AudioClient::outputLocalInjector(const AudioInjectorPointer& injector) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int AudioClient::getNumLocalInjectors() {
|
||||||
|
Lock lock(_injectorsMutex);
|
||||||
|
return _activeLocalAudioInjectors.size();
|
||||||
|
}
|
||||||
|
|
||||||
void AudioClient::outputFormatChanged() {
|
void AudioClient::outputFormatChanged() {
|
||||||
_outputFrameSize = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * OUTPUT_CHANNEL_COUNT * _outputFormat.sampleRate()) /
|
_outputFrameSize = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * OUTPUT_CHANNEL_COUNT * _outputFormat.sampleRate()) /
|
||||||
_desiredOutputFormat.sampleRate();
|
_desiredOutputFormat.sampleRate();
|
||||||
|
|
|
@ -181,6 +181,8 @@ public:
|
||||||
bool isHeadsetPluggedIn() { return _isHeadsetPluggedIn; }
|
bool isHeadsetPluggedIn() { return _isHeadsetPluggedIn; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
int getNumLocalInjectors();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
|
|
|
@ -24,9 +24,10 @@
|
||||||
#include "AudioRingBuffer.h"
|
#include "AudioRingBuffer.h"
|
||||||
#include "AudioLogging.h"
|
#include "AudioLogging.h"
|
||||||
#include "SoundCache.h"
|
#include "SoundCache.h"
|
||||||
#include "AudioSRC.h"
|
|
||||||
#include "AudioHelpers.h"
|
#include "AudioHelpers.h"
|
||||||
|
|
||||||
|
int metaType = qRegisterMetaType<AudioInjectorPointer>("AudioInjectorPointer");
|
||||||
|
|
||||||
AbstractAudioInterface* AudioInjector::_localAudioInterface{ nullptr };
|
AbstractAudioInterface* AudioInjector::_localAudioInterface{ nullptr };
|
||||||
|
|
||||||
AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs) {
|
AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs) {
|
||||||
|
@ -51,26 +52,30 @@ AudioInjector::AudioInjector(AudioDataPointer audioData, const AudioInjectorOpti
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioInjector::~AudioInjector() {
|
AudioInjector::~AudioInjector() {}
|
||||||
deleteLocalBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AudioInjector::stateHas(AudioInjectorState state) const {
|
bool AudioInjector::stateHas(AudioInjectorState state) const {
|
||||||
return (_state & state) == state;
|
return resultWithReadLock<bool>([&] {
|
||||||
|
return (_state & state) == state;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioInjector::setOptions(const AudioInjectorOptions& options) {
|
void AudioInjector::setOptions(const AudioInjectorOptions& options) {
|
||||||
// since options.stereo is computed from the audio stream,
|
// since options.stereo is computed from the audio stream,
|
||||||
// we need to copy it from existing options just in case.
|
// we need to copy it from existing options just in case.
|
||||||
bool currentlyStereo = _options.stereo;
|
withWriteLock([&] {
|
||||||
bool currentlyAmbisonic = _options.ambisonic;
|
bool currentlyStereo = _options.stereo;
|
||||||
_options = options;
|
bool currentlyAmbisonic = _options.ambisonic;
|
||||||
_options.stereo = currentlyStereo;
|
_options = options;
|
||||||
_options.ambisonic = currentlyAmbisonic;
|
_options.stereo = currentlyStereo;
|
||||||
|
_options.ambisonic = currentlyAmbisonic;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioInjector::finishNetworkInjection() {
|
void AudioInjector::finishNetworkInjection() {
|
||||||
_state |= AudioInjectorState::NetworkInjectionFinished;
|
withWriteLock([&] {
|
||||||
|
_state |= AudioInjectorState::NetworkInjectionFinished;
|
||||||
|
});
|
||||||
|
|
||||||
// if we are already finished with local
|
// if we are already finished with local
|
||||||
// injection, then we are finished
|
// injection, then we are finished
|
||||||
|
@ -80,35 +85,31 @@ void AudioInjector::finishNetworkInjection() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioInjector::finishLocalInjection() {
|
void AudioInjector::finishLocalInjection() {
|
||||||
_state |= AudioInjectorState::LocalInjectionFinished;
|
if (QThread::currentThread() != thread()) {
|
||||||
if(_options.localOnly || stateHas(AudioInjectorState::NetworkInjectionFinished)) {
|
QMetaObject::invokeMethod(this, "finishLocalInjection");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool localOnly = false;
|
||||||
|
withWriteLock([&] {
|
||||||
|
_state |= AudioInjectorState::LocalInjectionFinished;
|
||||||
|
localOnly = _options.localOnly;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(localOnly || stateHas(AudioInjectorState::NetworkInjectionFinished)) {
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioInjector::finish() {
|
void AudioInjector::finish() {
|
||||||
_state |= AudioInjectorState::Finished;
|
withWriteLock([&] {
|
||||||
|
_state |= AudioInjectorState::Finished;
|
||||||
|
});
|
||||||
emit finished();
|
emit finished();
|
||||||
|
_localBuffer = nullptr;
|
||||||
deleteLocalBuffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioInjector::restart() {
|
void AudioInjector::restart() {
|
||||||
// grab the AudioInjectorManager
|
|
||||||
auto injectorManager = DependencyManager::get<AudioInjectorManager>();
|
|
||||||
|
|
||||||
if (thread() != QThread::currentThread()) {
|
|
||||||
QMetaObject::invokeMethod(this, "restart");
|
|
||||||
|
|
||||||
if (!_options.localOnly) {
|
|
||||||
// notify the AudioInjectorManager to wake up in case it's waiting for new injectors
|
|
||||||
injectorManager->notifyInjectorReadyCondition();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset the current send offset to zero
|
// reset the current send offset to zero
|
||||||
_currentSendOffset = 0;
|
_currentSendOffset = 0;
|
||||||
|
|
||||||
|
@ -121,19 +122,23 @@ void AudioInjector::restart() {
|
||||||
|
|
||||||
// check our state to decide if we need extra handling for the restart request
|
// check our state to decide if we need extra handling for the restart request
|
||||||
if (stateHas(AudioInjectorState::Finished)) {
|
if (stateHas(AudioInjectorState::Finished)) {
|
||||||
if (!inject(&AudioInjectorManager::restartFinishedInjector)) {
|
if (!inject(&AudioInjectorManager::threadInjector)) {
|
||||||
qWarning() << "AudioInjector::restart failed to thread injector";
|
qWarning() << "AudioInjector::restart failed to thread injector";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&)) {
|
bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&)) {
|
||||||
_state = AudioInjectorState::NotFinished;
|
AudioInjectorOptions options;
|
||||||
|
withWriteLock([&] {
|
||||||
|
_state = AudioInjectorState::NotFinished;
|
||||||
|
options = _options;
|
||||||
|
});
|
||||||
|
|
||||||
int byteOffset = 0;
|
int byteOffset = 0;
|
||||||
if (_options.secondOffset > 0.0f) {
|
if (options.secondOffset > 0.0f) {
|
||||||
int numChannels = _options.ambisonic ? 4 : (_options.stereo ? 2 : 1);
|
int numChannels = options.ambisonic ? 4 : (options.stereo ? 2 : 1);
|
||||||
byteOffset = (int)(AudioConstants::SAMPLE_RATE * _options.secondOffset * numChannels);
|
byteOffset = (int)(AudioConstants::SAMPLE_RATE * options.secondOffset * numChannels);
|
||||||
byteOffset *= AudioConstants::SAMPLE_SIZE;
|
byteOffset *= AudioConstants::SAMPLE_SIZE;
|
||||||
}
|
}
|
||||||
_currentSendOffset = byteOffset;
|
_currentSendOffset = byteOffset;
|
||||||
|
@ -143,7 +148,7 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInj
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
if (!_options.localOnly) {
|
if (!options.localOnly) {
|
||||||
auto injectorManager = DependencyManager::get<AudioInjectorManager>();
|
auto injectorManager = DependencyManager::get<AudioInjectorManager>();
|
||||||
if (!(*injectorManager.*injection)(sharedFromThis())) {
|
if (!(*injectorManager.*injection)(sharedFromThis())) {
|
||||||
success = false;
|
success = false;
|
||||||
|
@ -158,7 +163,8 @@ bool AudioInjector::injectLocally() {
|
||||||
if (_localAudioInterface) {
|
if (_localAudioInterface) {
|
||||||
if (_audioData->getNumBytes() > 0) {
|
if (_audioData->getNumBytes() > 0) {
|
||||||
|
|
||||||
_localBuffer = new AudioInjectorLocalBuffer(_audioData);
|
_localBuffer = QSharedPointer<AudioInjectorLocalBuffer>(new AudioInjectorLocalBuffer(_audioData), &AudioInjectorLocalBuffer::deleteLater);
|
||||||
|
_localBuffer->moveToThread(thread());
|
||||||
|
|
||||||
_localBuffer->open(QIODevice::ReadOnly);
|
_localBuffer->open(QIODevice::ReadOnly);
|
||||||
_localBuffer->setShouldLoop(_options.loop);
|
_localBuffer->setShouldLoop(_options.loop);
|
||||||
|
@ -181,14 +187,6 @@ bool AudioInjector::injectLocally() {
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioInjector::deleteLocalBuffer() {
|
|
||||||
if (_localBuffer) {
|
|
||||||
_localBuffer->stop();
|
|
||||||
_localBuffer->deleteLater();
|
|
||||||
_localBuffer = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const uchar MAX_INJECTOR_VOLUME = packFloatGainToByte(1.0f);
|
const uchar MAX_INJECTOR_VOLUME = packFloatGainToByte(1.0f);
|
||||||
static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1;
|
static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1;
|
||||||
static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0;
|
static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0;
|
||||||
|
@ -220,6 +218,10 @@ int64_t AudioInjector::injectNextFrame() {
|
||||||
static int volumeOptionOffset = -1;
|
static int volumeOptionOffset = -1;
|
||||||
static int audioDataOffset = -1;
|
static int audioDataOffset = -1;
|
||||||
|
|
||||||
|
AudioInjectorOptions options = resultWithReadLock<AudioInjectorOptions>([&] {
|
||||||
|
return _options;
|
||||||
|
});
|
||||||
|
|
||||||
if (!_currentPacket) {
|
if (!_currentPacket) {
|
||||||
if (_currentSendOffset < 0 ||
|
if (_currentSendOffset < 0 ||
|
||||||
_currentSendOffset >= (int)_audioData->getNumBytes()) {
|
_currentSendOffset >= (int)_audioData->getNumBytes()) {
|
||||||
|
@ -253,7 +255,7 @@ int64_t AudioInjector::injectNextFrame() {
|
||||||
audioPacketStream << QUuid::createUuid();
|
audioPacketStream << QUuid::createUuid();
|
||||||
|
|
||||||
// pack the stereo/mono type of the stream
|
// pack the stereo/mono type of the stream
|
||||||
audioPacketStream << _options.stereo;
|
audioPacketStream << options.stereo;
|
||||||
|
|
||||||
// pack the flag for loopback, if requested
|
// pack the flag for loopback, if requested
|
||||||
loopbackOptionOffset = _currentPacket->pos();
|
loopbackOptionOffset = _currentPacket->pos();
|
||||||
|
@ -262,15 +264,16 @@ int64_t AudioInjector::injectNextFrame() {
|
||||||
|
|
||||||
// pack the position for injected audio
|
// pack the position for injected audio
|
||||||
positionOptionOffset = _currentPacket->pos();
|
positionOptionOffset = _currentPacket->pos();
|
||||||
audioPacketStream.writeRawData(reinterpret_cast<const char*>(&_options.position),
|
audioPacketStream.writeRawData(reinterpret_cast<const char*>(&options.position),
|
||||||
sizeof(_options.position));
|
sizeof(options.position));
|
||||||
|
|
||||||
// pack our orientation for injected audio
|
// pack our orientation for injected audio
|
||||||
audioPacketStream.writeRawData(reinterpret_cast<const char*>(&_options.orientation),
|
audioPacketStream.writeRawData(reinterpret_cast<const char*>(&options.orientation),
|
||||||
sizeof(_options.orientation));
|
sizeof(options.orientation));
|
||||||
|
|
||||||
|
audioPacketStream.writeRawData(reinterpret_cast<const char*>(&options.position),
|
||||||
|
sizeof(options.position));
|
||||||
|
|
||||||
audioPacketStream.writeRawData(reinterpret_cast<const char*>(&_options.position),
|
|
||||||
sizeof(_options.position));
|
|
||||||
glm::vec3 boxCorner = glm::vec3(0);
|
glm::vec3 boxCorner = glm::vec3(0);
|
||||||
audioPacketStream.writeRawData(reinterpret_cast<const char*>(&boxCorner),
|
audioPacketStream.writeRawData(reinterpret_cast<const char*>(&boxCorner),
|
||||||
sizeof(glm::vec3));
|
sizeof(glm::vec3));
|
||||||
|
@ -283,7 +286,7 @@ int64_t AudioInjector::injectNextFrame() {
|
||||||
volumeOptionOffset = _currentPacket->pos();
|
volumeOptionOffset = _currentPacket->pos();
|
||||||
quint8 volume = MAX_INJECTOR_VOLUME;
|
quint8 volume = MAX_INJECTOR_VOLUME;
|
||||||
audioPacketStream << volume;
|
audioPacketStream << volume;
|
||||||
audioPacketStream << _options.ignorePenumbra;
|
audioPacketStream << options.ignorePenumbra;
|
||||||
|
|
||||||
audioDataOffset = _currentPacket->pos();
|
audioDataOffset = _currentPacket->pos();
|
||||||
|
|
||||||
|
@ -313,10 +316,10 @@ int64_t AudioInjector::injectNextFrame() {
|
||||||
_currentPacket->writePrimitive((uchar)(_localAudioInterface && _localAudioInterface->shouldLoopbackInjectors()));
|
_currentPacket->writePrimitive((uchar)(_localAudioInterface && _localAudioInterface->shouldLoopbackInjectors()));
|
||||||
|
|
||||||
_currentPacket->seek(positionOptionOffset);
|
_currentPacket->seek(positionOptionOffset);
|
||||||
_currentPacket->writePrimitive(_options.position);
|
_currentPacket->writePrimitive(options.position);
|
||||||
_currentPacket->writePrimitive(_options.orientation);
|
_currentPacket->writePrimitive(options.orientation);
|
||||||
|
|
||||||
quint8 volume = packFloatGainToByte(_options.volume);
|
quint8 volume = packFloatGainToByte(options.volume);
|
||||||
_currentPacket->seek(volumeOptionOffset);
|
_currentPacket->seek(volumeOptionOffset);
|
||||||
_currentPacket->writePrimitive(volume);
|
_currentPacket->writePrimitive(volume);
|
||||||
|
|
||||||
|
@ -326,8 +329,8 @@ int64_t AudioInjector::injectNextFrame() {
|
||||||
// Might be a reasonable place to do the encode step here.
|
// Might be a reasonable place to do the encode step here.
|
||||||
QByteArray decodedAudio;
|
QByteArray decodedAudio;
|
||||||
|
|
||||||
int totalBytesLeftToCopy = (_options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
int totalBytesLeftToCopy = (options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
||||||
if (!_options.loop) {
|
if (!options.loop) {
|
||||||
// If we aren't looping, let's make sure we don't read past the end
|
// If we aren't looping, let's make sure we don't read past the end
|
||||||
int bytesLeftToRead = _audioData->getNumBytes() - _currentSendOffset;
|
int bytesLeftToRead = _audioData->getNumBytes() - _currentSendOffset;
|
||||||
totalBytesLeftToCopy = std::min(totalBytesLeftToCopy, bytesLeftToRead);
|
totalBytesLeftToCopy = std::min(totalBytesLeftToCopy, bytesLeftToRead);
|
||||||
|
@ -342,14 +345,16 @@ int64_t AudioInjector::injectNextFrame() {
|
||||||
auto samplesOut = reinterpret_cast<AudioSample*>(decodedAudio.data());
|
auto samplesOut = reinterpret_cast<AudioSample*>(decodedAudio.data());
|
||||||
|
|
||||||
// Copy and Measure the loudness of this frame
|
// Copy and Measure the loudness of this frame
|
||||||
_loudness = 0.0f;
|
withWriteLock([&] {
|
||||||
for (int i = 0; i < samplesLeftToCopy; ++i) {
|
_loudness = 0.0f;
|
||||||
auto index = (currentSample + i) % _audioData->getNumSamples();
|
for (int i = 0; i < samplesLeftToCopy; ++i) {
|
||||||
auto sample = samples[index];
|
auto index = (currentSample + i) % _audioData->getNumSamples();
|
||||||
samplesOut[i] = sample;
|
auto sample = samples[index];
|
||||||
_loudness += abs(sample) / (AudioConstants::MAX_SAMPLE_VALUE / 2.0f);
|
samplesOut[i] = sample;
|
||||||
}
|
_loudness += abs(sample) / (AudioConstants::MAX_SAMPLE_VALUE / 2.0f);
|
||||||
_loudness /= (float)samplesLeftToCopy;
|
}
|
||||||
|
_loudness /= (float)samplesLeftToCopy;
|
||||||
|
});
|
||||||
_currentSendOffset = (_currentSendOffset + totalBytesLeftToCopy) %
|
_currentSendOffset = (_currentSendOffset + totalBytesLeftToCopy) %
|
||||||
_audioData->getNumBytes();
|
_audioData->getNumBytes();
|
||||||
|
|
||||||
|
@ -371,7 +376,7 @@ int64_t AudioInjector::injectNextFrame() {
|
||||||
_outgoingSequenceNumber++;
|
_outgoingSequenceNumber++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_currentSendOffset == 0 && !_options.loop) {
|
if (_currentSendOffset == 0 && !options.loop) {
|
||||||
finishNetworkInjection();
|
finishNetworkInjection();
|
||||||
return NEXT_FRAME_DELTA_ERROR_OR_FINISHED;
|
return NEXT_FRAME_DELTA_ERROR_OR_FINISHED;
|
||||||
}
|
}
|
||||||
|
@ -391,134 +396,10 @@ int64_t AudioInjector::injectNextFrame() {
|
||||||
// If we are falling behind by more frames than our threshold, let's skip the frames ahead
|
// If we are falling behind by more frames than our threshold, let's skip the frames ahead
|
||||||
qCDebug(audio) << this << "injectNextFrame() skipping ahead, fell behind by " << (currentFrameBasedOnElapsedTime - _nextFrame) << " frames";
|
qCDebug(audio) << this << "injectNextFrame() skipping ahead, fell behind by " << (currentFrameBasedOnElapsedTime - _nextFrame) << " frames";
|
||||||
_nextFrame = currentFrameBasedOnElapsedTime;
|
_nextFrame = currentFrameBasedOnElapsedTime;
|
||||||
_currentSendOffset = _nextFrame * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * (_options.stereo ? 2 : 1) % _audioData->getNumBytes();
|
_currentSendOffset = _nextFrame * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * (options.stereo ? 2 : 1) % _audioData->getNumBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t playNextFrameAt = ++_nextFrame * AudioConstants::NETWORK_FRAME_USECS;
|
int64_t playNextFrameAt = ++_nextFrame * AudioConstants::NETWORK_FRAME_USECS;
|
||||||
|
|
||||||
return std::max(INT64_C(0), playNextFrameAt - currentTime);
|
return std::max(INT64_C(0), playNextFrameAt - currentTime);
|
||||||
}
|
|
||||||
|
|
||||||
void AudioInjector::stop() {
|
|
||||||
// trigger a call on the injector's thread to change state to finished
|
|
||||||
QMetaObject::invokeMethod(this, "finish");
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioInjector::triggerDeleteAfterFinish() {
|
|
||||||
// make sure this fires on the AudioInjector thread
|
|
||||||
if (thread() != QThread::currentThread()) {
|
|
||||||
QMetaObject::invokeMethod(this, "triggerDeleteAfterFinish", Qt::QueuedConnection);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stateHas(AudioInjectorState::Finished)) {
|
|
||||||
stop();
|
|
||||||
} else {
|
|
||||||
_state |= AudioInjectorState::PendingDelete;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioInjectorPointer AudioInjector::playSoundAndDelete(SharedSoundPointer sound, const AudioInjectorOptions& options) {
|
|
||||||
AudioInjectorPointer injector = playSound(sound, options);
|
|
||||||
|
|
||||||
if (injector) {
|
|
||||||
injector->_state |= AudioInjectorState::PendingDelete;
|
|
||||||
}
|
|
||||||
|
|
||||||
return injector;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound, const AudioInjectorOptions& options) {
|
|
||||||
if (!sound || !sound->isReady()) {
|
|
||||||
return AudioInjectorPointer();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.pitch == 1.0f) {
|
|
||||||
|
|
||||||
AudioInjectorPointer injector = AudioInjectorPointer::create(sound, options);
|
|
||||||
|
|
||||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
|
||||||
qWarning() << "AudioInjector::playSound failed to thread injector";
|
|
||||||
}
|
|
||||||
return injector;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
using AudioConstants::AudioSample;
|
|
||||||
using AudioConstants::SAMPLE_RATE;
|
|
||||||
const int standardRate = SAMPLE_RATE;
|
|
||||||
// limit pitch to 4 octaves
|
|
||||||
const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
|
|
||||||
const int resampledRate = glm::round(SAMPLE_RATE / pitch);
|
|
||||||
|
|
||||||
auto audioData = sound->getAudioData();
|
|
||||||
auto numChannels = audioData->getNumChannels();
|
|
||||||
auto numFrames = audioData->getNumFrames();
|
|
||||||
|
|
||||||
AudioSRC resampler(standardRate, resampledRate, numChannels);
|
|
||||||
|
|
||||||
// create a resampled buffer that is guaranteed to be large enough
|
|
||||||
const int maxOutputFrames = resampler.getMaxOutput(numFrames);
|
|
||||||
const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample);
|
|
||||||
QByteArray resampledBuffer(maxOutputSize, '\0');
|
|
||||||
auto bufferPtr = reinterpret_cast<AudioSample*>(resampledBuffer.data());
|
|
||||||
|
|
||||||
resampler.render(audioData->data(), bufferPtr, numFrames);
|
|
||||||
|
|
||||||
int numSamples = maxOutputFrames * numChannels;
|
|
||||||
auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);
|
|
||||||
|
|
||||||
AudioInjectorPointer injector = AudioInjectorPointer::create(newAudioData, options);
|
|
||||||
|
|
||||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
|
||||||
qWarning() << "AudioInjector::playSound failed to thread pitch-shifted injector";
|
|
||||||
}
|
|
||||||
return injector;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioInjectorPointer AudioInjector::playSoundAndDelete(AudioDataPointer audioData, const AudioInjectorOptions& options) {
|
|
||||||
AudioInjectorPointer injector = playSound(audioData, options);
|
|
||||||
|
|
||||||
if (injector) {
|
|
||||||
injector->_state |= AudioInjectorState::PendingDelete;
|
|
||||||
}
|
|
||||||
|
|
||||||
return injector;
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioInjectorPointer AudioInjector::playSound(AudioDataPointer audioData, const AudioInjectorOptions& options) {
|
|
||||||
if (options.pitch == 1.0f) {
|
|
||||||
AudioInjectorPointer injector = AudioInjectorPointer::create(audioData, options);
|
|
||||||
|
|
||||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
|
||||||
qWarning() << "AudioInjector::playSound failed to thread pitch-shifted injector";
|
|
||||||
}
|
|
||||||
return injector;
|
|
||||||
} else {
|
|
||||||
using AudioConstants::AudioSample;
|
|
||||||
using AudioConstants::SAMPLE_RATE;
|
|
||||||
const int standardRate = SAMPLE_RATE;
|
|
||||||
// limit pitch to 4 octaves
|
|
||||||
const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
|
|
||||||
const int resampledRate = glm::round(SAMPLE_RATE / pitch);
|
|
||||||
|
|
||||||
auto numChannels = audioData->getNumChannels();
|
|
||||||
auto numFrames = audioData->getNumFrames();
|
|
||||||
|
|
||||||
AudioSRC resampler(standardRate, resampledRate, numChannels);
|
|
||||||
|
|
||||||
// create a resampled buffer that is guaranteed to be large enough
|
|
||||||
const int maxOutputFrames = resampler.getMaxOutput(numFrames);
|
|
||||||
const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample);
|
|
||||||
QByteArray resampledBuffer(maxOutputSize, '\0');
|
|
||||||
auto bufferPtr = reinterpret_cast<AudioSample*>(resampledBuffer.data());
|
|
||||||
|
|
||||||
resampler.render(audioData->data(), bufferPtr, numFrames);
|
|
||||||
|
|
||||||
int numSamples = maxOutputFrames * numChannels;
|
|
||||||
auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);
|
|
||||||
|
|
||||||
return AudioInjector::playSound(newAudioData, options);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -19,6 +19,8 @@
|
||||||
#include <QtCore/QSharedPointer>
|
#include <QtCore/QSharedPointer>
|
||||||
#include <QtCore/QThread>
|
#include <QtCore/QThread>
|
||||||
|
|
||||||
|
#include <shared/ReadWriteLockable.h>
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtx/quaternion.hpp>
|
#include <glm/gtx/quaternion.hpp>
|
||||||
|
|
||||||
|
@ -49,7 +51,7 @@ AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs)
|
||||||
|
|
||||||
// In order to make scripting cleaner for the AudioInjector, the script now holds on to the AudioInjector object
|
// In order to make scripting cleaner for the AudioInjector, the script now holds on to the AudioInjector object
|
||||||
// until it dies.
|
// until it dies.
|
||||||
class AudioInjector : public QObject, public QEnableSharedFromThis<AudioInjector> {
|
class AudioInjector : public QObject, public QEnableSharedFromThis<AudioInjector>, public ReadWriteLockable {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AudioInjector(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions);
|
AudioInjector(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions);
|
||||||
|
@ -61,40 +63,34 @@ public:
|
||||||
int getCurrentSendOffset() const { return _currentSendOffset; }
|
int getCurrentSendOffset() const { return _currentSendOffset; }
|
||||||
void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; }
|
void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; }
|
||||||
|
|
||||||
AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; }
|
QSharedPointer<AudioInjectorLocalBuffer> getLocalBuffer() const { return _localBuffer; }
|
||||||
AudioHRTF& getLocalHRTF() { return _localHRTF; }
|
AudioHRTF& getLocalHRTF() { return _localHRTF; }
|
||||||
AudioFOA& getLocalFOA() { return _localFOA; }
|
AudioFOA& getLocalFOA() { return _localFOA; }
|
||||||
|
|
||||||
bool isLocalOnly() const { return _options.localOnly; }
|
float getLoudness() const { return resultWithReadLock<float>([&] { return _loudness; }); }
|
||||||
float getVolume() const { return _options.volume; }
|
bool isPlaying() const { return !stateHas(AudioInjectorState::Finished); }
|
||||||
bool isPositionSet() const { return _options.positionSet; }
|
|
||||||
glm::vec3 getPosition() const { return _options.position; }
|
bool isLocalOnly() const { return resultWithReadLock<bool>([&] { return _options.localOnly; }); }
|
||||||
glm::quat getOrientation() const { return _options.orientation; }
|
float getVolume() const { return resultWithReadLock<float>([&] { return _options.volume; }); }
|
||||||
bool isStereo() const { return _options.stereo; }
|
bool isPositionSet() const { return resultWithReadLock<bool>([&] { return _options.positionSet; }); }
|
||||||
bool isAmbisonic() const { return _options.ambisonic; }
|
glm::vec3 getPosition() const { return resultWithReadLock<glm::vec3>([&] { return _options.position; }); }
|
||||||
|
glm::quat getOrientation() const { return resultWithReadLock<glm::quat>([&] { return _options.orientation; }); }
|
||||||
|
bool isStereo() const { return resultWithReadLock<bool>([&] { return _options.stereo; }); }
|
||||||
|
bool isAmbisonic() const { return resultWithReadLock<bool>([&] { return _options.ambisonic; }); }
|
||||||
|
|
||||||
|
AudioInjectorOptions getOptions() const { return resultWithReadLock<AudioInjectorOptions>([&] { return _options; }); }
|
||||||
|
void setOptions(const AudioInjectorOptions& options);
|
||||||
|
|
||||||
bool stateHas(AudioInjectorState state) const ;
|
bool stateHas(AudioInjectorState state) const ;
|
||||||
static void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; }
|
static void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; }
|
||||||
|
|
||||||
static AudioInjectorPointer playSoundAndDelete(SharedSoundPointer sound, const AudioInjectorOptions& options);
|
void restart();
|
||||||
static AudioInjectorPointer playSound(SharedSoundPointer sound, const AudioInjectorOptions& options);
|
void finish();
|
||||||
static AudioInjectorPointer playSoundAndDelete(AudioDataPointer audioData, const AudioInjectorOptions& options);
|
|
||||||
static AudioInjectorPointer playSound(AudioDataPointer audioData, const AudioInjectorOptions& options);
|
void finishNetworkInjection();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void restart();
|
|
||||||
|
|
||||||
void stop();
|
|
||||||
void triggerDeleteAfterFinish();
|
|
||||||
|
|
||||||
const AudioInjectorOptions& getOptions() const { return _options; }
|
|
||||||
void setOptions(const AudioInjectorOptions& options);
|
|
||||||
|
|
||||||
float getLoudness() const { return _loudness; }
|
|
||||||
bool isPlaying() const { return !stateHas(AudioInjectorState::Finished); }
|
|
||||||
void finish();
|
|
||||||
void finishLocalInjection();
|
void finishLocalInjection();
|
||||||
void finishNetworkInjection();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void finished();
|
void finished();
|
||||||
|
@ -104,7 +100,6 @@ private:
|
||||||
int64_t injectNextFrame();
|
int64_t injectNextFrame();
|
||||||
bool inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&));
|
bool inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&));
|
||||||
bool injectLocally();
|
bool injectLocally();
|
||||||
void deleteLocalBuffer();
|
|
||||||
|
|
||||||
static AbstractAudioInterface* _localAudioInterface;
|
static AbstractAudioInterface* _localAudioInterface;
|
||||||
|
|
||||||
|
@ -116,7 +111,7 @@ private:
|
||||||
float _loudness { 0.0f };
|
float _loudness { 0.0f };
|
||||||
int _currentSendOffset { 0 };
|
int _currentSendOffset { 0 };
|
||||||
std::unique_ptr<NLPacket> _currentPacket { nullptr };
|
std::unique_ptr<NLPacket> _currentPacket { nullptr };
|
||||||
AudioInjectorLocalBuffer* _localBuffer { nullptr };
|
QSharedPointer<AudioInjectorLocalBuffer> _localBuffer { nullptr };
|
||||||
|
|
||||||
int64_t _nextFrame { 0 };
|
int64_t _nextFrame { 0 };
|
||||||
std::unique_ptr<QElapsedTimer> _frameTimer { nullptr };
|
std::unique_ptr<QElapsedTimer> _frameTimer { nullptr };
|
||||||
|
@ -128,4 +123,6 @@ private:
|
||||||
friend class AudioInjectorManager;
|
friend class AudioInjectorManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(AudioInjectorPointer)
|
||||||
|
|
||||||
#endif // hifi_AudioInjector_h
|
#endif // hifi_AudioInjector_h
|
||||||
|
|
|
@ -16,6 +16,10 @@ AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(AudioDataPointer audioData) :
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioInjectorLocalBuffer::~AudioInjectorLocalBuffer() {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
void AudioInjectorLocalBuffer::stop() {
|
void AudioInjectorLocalBuffer::stop() {
|
||||||
_isStopped = true;
|
_isStopped = true;
|
||||||
|
|
||||||
|
@ -30,9 +34,8 @@ bool AudioInjectorLocalBuffer::seek(qint64 pos) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) {
|
qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) {
|
||||||
if (!_isStopped) {
|
if (!_isStopped && _audioData) {
|
||||||
|
|
||||||
// first copy to the end of the raw audio
|
// first copy to the end of the raw audio
|
||||||
int bytesToEnd = (int)_audioData->getNumBytes() - _currentOffset;
|
int bytesToEnd = (int)_audioData->getNumBytes() - _currentOffset;
|
||||||
|
|
|
@ -22,6 +22,7 @@ class AudioInjectorLocalBuffer : public QIODevice {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AudioInjectorLocalBuffer(AudioDataPointer audioData);
|
AudioInjectorLocalBuffer(AudioDataPointer audioData);
|
||||||
|
~AudioInjectorLocalBuffer();
|
||||||
|
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,14 @@
|
||||||
#include <QtCore/QCoreApplication>
|
#include <QtCore/QCoreApplication>
|
||||||
|
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
#include <shared/QtHelpers.h>
|
||||||
|
|
||||||
#include "AudioConstants.h"
|
#include "AudioConstants.h"
|
||||||
#include "AudioInjector.h"
|
#include "AudioInjector.h"
|
||||||
#include "AudioLogging.h"
|
#include "AudioLogging.h"
|
||||||
|
|
||||||
|
#include "AudioSRC.h"
|
||||||
|
|
||||||
AudioInjectorManager::~AudioInjectorManager() {
|
AudioInjectorManager::~AudioInjectorManager() {
|
||||||
_shouldStop = true;
|
_shouldStop = true;
|
||||||
|
|
||||||
|
@ -30,7 +33,7 @@ AudioInjectorManager::~AudioInjectorManager() {
|
||||||
auto& timePointerPair = _injectors.top();
|
auto& timePointerPair = _injectors.top();
|
||||||
|
|
||||||
// ask it to stop and be deleted
|
// ask it to stop and be deleted
|
||||||
timePointerPair.second->stop();
|
timePointerPair.second->finish();
|
||||||
|
|
||||||
_injectors.pop();
|
_injectors.pop();
|
||||||
}
|
}
|
||||||
|
@ -46,6 +49,8 @@ AudioInjectorManager::~AudioInjectorManager() {
|
||||||
_thread->quit();
|
_thread->quit();
|
||||||
_thread->wait();
|
_thread->wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moveToThread(qApp->thread());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioInjectorManager::createThread() {
|
void AudioInjectorManager::createThread() {
|
||||||
|
@ -55,6 +60,8 @@ void AudioInjectorManager::createThread() {
|
||||||
// when the thread is started, have it call our run to handle injection of audio
|
// when the thread is started, have it call our run to handle injection of audio
|
||||||
connect(_thread, &QThread::started, this, &AudioInjectorManager::run, Qt::DirectConnection);
|
connect(_thread, &QThread::started, this, &AudioInjectorManager::run, Qt::DirectConnection);
|
||||||
|
|
||||||
|
moveToThread(_thread);
|
||||||
|
|
||||||
// start the thread
|
// start the thread
|
||||||
_thread->start();
|
_thread->start();
|
||||||
}
|
}
|
||||||
|
@ -141,36 +148,7 @@ bool AudioInjectorManager::wouldExceedLimits() { // Should be called inside of a
|
||||||
|
|
||||||
bool AudioInjectorManager::threadInjector(const AudioInjectorPointer& injector) {
|
bool AudioInjectorManager::threadInjector(const AudioInjectorPointer& injector) {
|
||||||
if (_shouldStop) {
|
if (_shouldStop) {
|
||||||
qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
|
qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// guard the injectors vector with a mutex
|
|
||||||
Lock lock(_injectorsMutex);
|
|
||||||
|
|
||||||
if (wouldExceedLimits()) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
if (!_thread) {
|
|
||||||
createThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
// move the injector to the QThread
|
|
||||||
injector->moveToThread(_thread);
|
|
||||||
|
|
||||||
// add the injector to the queue with a send timestamp of now
|
|
||||||
_injectors.emplace(usecTimestampNow(), injector);
|
|
||||||
|
|
||||||
// notify our wait condition so we can inject two frames for this injector immediately
|
|
||||||
_injectorReady.notify_one();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AudioInjectorManager::restartFinishedInjector(const AudioInjectorPointer& injector) {
|
|
||||||
if (_shouldStop) {
|
|
||||||
qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,3 +166,192 @@ bool AudioInjectorManager::restartFinishedInjector(const AudioInjectorPointer& i
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioInjectorPointer AudioInjectorManager::playSound(const SharedSoundPointer& sound, const AudioInjectorOptions& options, bool setPendingDelete) {
|
||||||
|
if (_shouldStop) {
|
||||||
|
qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioInjectorPointer injector = nullptr;
|
||||||
|
if (sound && sound->isReady()) {
|
||||||
|
if (options.pitch == 1.0f) {
|
||||||
|
injector = QSharedPointer<AudioInjector>(new AudioInjector(sound, options), &AudioInjector::deleteLater);
|
||||||
|
} else {
|
||||||
|
using AudioConstants::AudioSample;
|
||||||
|
using AudioConstants::SAMPLE_RATE;
|
||||||
|
const int standardRate = SAMPLE_RATE;
|
||||||
|
// limit pitch to 4 octaves
|
||||||
|
const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
|
||||||
|
const int resampledRate = glm::round(SAMPLE_RATE / pitch);
|
||||||
|
|
||||||
|
auto audioData = sound->getAudioData();
|
||||||
|
auto numChannels = audioData->getNumChannels();
|
||||||
|
auto numFrames = audioData->getNumFrames();
|
||||||
|
|
||||||
|
AudioSRC resampler(standardRate, resampledRate, numChannels);
|
||||||
|
|
||||||
|
// create a resampled buffer that is guaranteed to be large enough
|
||||||
|
const int maxOutputFrames = resampler.getMaxOutput(numFrames);
|
||||||
|
const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample);
|
||||||
|
QByteArray resampledBuffer(maxOutputSize, '\0');
|
||||||
|
auto bufferPtr = reinterpret_cast<AudioSample*>(resampledBuffer.data());
|
||||||
|
|
||||||
|
resampler.render(audioData->data(), bufferPtr, numFrames);
|
||||||
|
|
||||||
|
int numSamples = maxOutputFrames * numChannels;
|
||||||
|
auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);
|
||||||
|
|
||||||
|
injector = QSharedPointer<AudioInjector>(new AudioInjector(newAudioData, options), &AudioInjector::deleteLater);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!injector) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setPendingDelete) {
|
||||||
|
injector->_state |= AudioInjectorState::PendingDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
injector->moveToThread(_thread);
|
||||||
|
injector->inject(&AudioInjectorManager::threadInjector);
|
||||||
|
|
||||||
|
return injector;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioInjectorPointer AudioInjectorManager::playSound(const AudioDataPointer& audioData, const AudioInjectorOptions& options, bool setPendingDelete) {
|
||||||
|
if (_shouldStop) {
|
||||||
|
qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioInjectorPointer injector = nullptr;
|
||||||
|
if (options.pitch == 1.0f) {
|
||||||
|
injector = QSharedPointer<AudioInjector>(new AudioInjector(audioData, options), &AudioInjector::deleteLater);
|
||||||
|
} else {
|
||||||
|
using AudioConstants::AudioSample;
|
||||||
|
using AudioConstants::SAMPLE_RATE;
|
||||||
|
const int standardRate = SAMPLE_RATE;
|
||||||
|
// limit pitch to 4 octaves
|
||||||
|
const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
|
||||||
|
const int resampledRate = glm::round(SAMPLE_RATE / pitch);
|
||||||
|
|
||||||
|
auto numChannels = audioData->getNumChannels();
|
||||||
|
auto numFrames = audioData->getNumFrames();
|
||||||
|
|
||||||
|
AudioSRC resampler(standardRate, resampledRate, numChannels);
|
||||||
|
|
||||||
|
// create a resampled buffer that is guaranteed to be large enough
|
||||||
|
const int maxOutputFrames = resampler.getMaxOutput(numFrames);
|
||||||
|
const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample);
|
||||||
|
QByteArray resampledBuffer(maxOutputSize, '\0');
|
||||||
|
auto bufferPtr = reinterpret_cast<AudioSample*>(resampledBuffer.data());
|
||||||
|
|
||||||
|
resampler.render(audioData->data(), bufferPtr, numFrames);
|
||||||
|
|
||||||
|
int numSamples = maxOutputFrames * numChannels;
|
||||||
|
auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);
|
||||||
|
|
||||||
|
injector = QSharedPointer<AudioInjector>(new AudioInjector(newAudioData, options), &AudioInjector::deleteLater);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!injector) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setPendingDelete) {
|
||||||
|
injector->_state |= AudioInjectorState::PendingDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
injector->moveToThread(_thread);
|
||||||
|
injector->inject(&AudioInjectorManager::threadInjector);
|
||||||
|
|
||||||
|
return injector;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioInjectorManager::setOptionsAndRestart(const AudioInjectorPointer& injector, const AudioInjectorOptions& options) {
|
||||||
|
if (!injector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QThread::currentThread() != _thread) {
|
||||||
|
QMetaObject::invokeMethod(this, "setOptionsAndRestart", Q_ARG(const AudioInjectorPointer&, injector), Q_ARG(const AudioInjectorOptions&, options));
|
||||||
|
_injectorReady.notify_one();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
injector->setOptions(options);
|
||||||
|
injector->restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioInjectorManager::restart(const AudioInjectorPointer& injector) {
|
||||||
|
if (!injector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QThread::currentThread() != _thread) {
|
||||||
|
QMetaObject::invokeMethod(this, "restart", Q_ARG(const AudioInjectorPointer&, injector));
|
||||||
|
_injectorReady.notify_one();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
injector->restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioInjectorManager::setOptions(const AudioInjectorPointer& injector, const AudioInjectorOptions& options) {
|
||||||
|
if (!injector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QThread::currentThread() != _thread) {
|
||||||
|
QMetaObject::invokeMethod(this, "setOptions", Q_ARG(const AudioInjectorPointer&, injector), Q_ARG(const AudioInjectorOptions&, options));
|
||||||
|
_injectorReady.notify_one();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
injector->setOptions(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioInjectorOptions AudioInjectorManager::getOptions(const AudioInjectorPointer& injector) {
|
||||||
|
if (!injector) {
|
||||||
|
return AudioInjectorOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
return injector->getOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
float AudioInjectorManager::getLoudness(const AudioInjectorPointer& injector) {
|
||||||
|
if (!injector) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return injector->getLoudness();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioInjectorManager::isPlaying(const AudioInjectorPointer& injector) {
|
||||||
|
if (!injector) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return injector->isPlaying();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioInjectorManager::stop(const AudioInjectorPointer& injector) {
|
||||||
|
if (!injector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QThread::currentThread() != _thread) {
|
||||||
|
QMetaObject::invokeMethod(this, "stop", Q_ARG(const AudioInjectorPointer&, injector));
|
||||||
|
_injectorReady.notify_one();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
injector->finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AudioInjectorManager::getNumInjectors() {
|
||||||
|
Lock lock(_injectorsMutex);
|
||||||
|
return _injectors.size();
|
||||||
|
}
|
|
@ -30,8 +30,27 @@ class AudioInjectorManager : public QObject, public Dependency {
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
public:
|
public:
|
||||||
~AudioInjectorManager();
|
~AudioInjectorManager();
|
||||||
|
|
||||||
|
AudioInjectorPointer playSound(const SharedSoundPointer& sound, const AudioInjectorOptions& options, bool setPendingDelete = false);
|
||||||
|
AudioInjectorPointer playSound(const AudioDataPointer& audioData, const AudioInjectorOptions& options, bool setPendingDelete = false);
|
||||||
|
|
||||||
|
size_t getNumInjectors();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setOptionsAndRestart(const AudioInjectorPointer& injector, const AudioInjectorOptions& options);
|
||||||
|
void restart(const AudioInjectorPointer& injector);
|
||||||
|
|
||||||
|
void setOptions(const AudioInjectorPointer& injector, const AudioInjectorOptions& options);
|
||||||
|
AudioInjectorOptions getOptions(const AudioInjectorPointer& injector);
|
||||||
|
|
||||||
|
float getLoudness(const AudioInjectorPointer& injector);
|
||||||
|
bool isPlaying(const AudioInjectorPointer& injector);
|
||||||
|
|
||||||
|
void stop(const AudioInjectorPointer& injector);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
using TimeInjectorPointerPair = std::pair<uint64_t, AudioInjectorPointer>;
|
using TimeInjectorPointerPair = std::pair<uint64_t, AudioInjectorPointer>;
|
||||||
|
@ -49,11 +68,10 @@ private:
|
||||||
using Lock = std::unique_lock<Mutex>;
|
using Lock = std::unique_lock<Mutex>;
|
||||||
|
|
||||||
bool threadInjector(const AudioInjectorPointer& injector);
|
bool threadInjector(const AudioInjectorPointer& injector);
|
||||||
bool restartFinishedInjector(const AudioInjectorPointer& injector);
|
|
||||||
void notifyInjectorReadyCondition() { _injectorReady.notify_one(); }
|
void notifyInjectorReadyCondition() { _injectorReady.notify_one(); }
|
||||||
bool wouldExceedLimits();
|
bool wouldExceedLimits();
|
||||||
|
|
||||||
AudioInjectorManager() {};
|
AudioInjectorManager() { createThread(); }
|
||||||
AudioInjectorManager(const AudioInjectorManager&) = delete;
|
AudioInjectorManager(const AudioInjectorManager&) = delete;
|
||||||
AudioInjectorManager& operator=(const AudioInjectorManager&) = delete;
|
AudioInjectorManager& operator=(const AudioInjectorManager&) = delete;
|
||||||
|
|
||||||
|
|
|
@ -376,7 +376,7 @@ bool Avatar::applyGrabChanges() {
|
||||||
const EntityItemPointer& entity = std::dynamic_pointer_cast<EntityItem>(target);
|
const EntityItemPointer& entity = std::dynamic_pointer_cast<EntityItem>(target);
|
||||||
if (entity && entity->getEntityHostType() == entity::HostType::AVATAR && entity->getSimulationOwner().getID() == getID()) {
|
if (entity && entity->getEntityHostType() == entity::HostType::AVATAR && entity->getSimulationOwner().getID() == getID()) {
|
||||||
EntityItemProperties properties = entity->getProperties();
|
EntityItemProperties properties = entity->getProperties();
|
||||||
sendPacket(entity->getID(), properties);
|
sendPacket(entity->getID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -605,7 +605,7 @@ protected:
|
||||||
|
|
||||||
// protected methods...
|
// protected methods...
|
||||||
bool isLookingAtMe(AvatarSharedPointer avatar) const;
|
bool isLookingAtMe(AvatarSharedPointer avatar) const;
|
||||||
virtual void sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const { }
|
virtual void sendPacket(const QUuid& entityID) const { }
|
||||||
bool applyGrabChanges();
|
bool applyGrabChanges();
|
||||||
void relayJointDataToChildren();
|
void relayJointDataToChildren();
|
||||||
|
|
||||||
|
|
|
@ -1105,7 +1105,7 @@ void EntityTreeRenderer::playEntityCollisionSound(const EntityItemPointer& entit
|
||||||
options.volume = volume;
|
options.volume = volume;
|
||||||
options.pitch = 1.0f / stretchFactor;
|
options.pitch = 1.0f / stretchFactor;
|
||||||
|
|
||||||
AudioInjector::playSoundAndDelete(collisionSound, options);
|
DependencyManager::get<AudioInjectorManager>()->playSound(collisionSound, options, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB,
|
void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB,
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
#include <QtCore/QStack>
|
#include <QtCore/QStack>
|
||||||
#include <QtGui/QMouseEvent>
|
#include <QtGui/QMouseEvent>
|
||||||
|
|
||||||
#include <AbstractAudioInterface.h>
|
#include <AudioInjectorManager.h>
|
||||||
#include <EntityScriptingInterface.h> // for RayToEntityIntersectionResult
|
#include <EntityScriptingInterface.h> // for RayToEntityIntersectionResult
|
||||||
#include <EntityTree.h>
|
#include <EntityTree.h>
|
||||||
#include <PointerEvent.h>
|
#include <PointerEvent.h>
|
||||||
|
|
|
@ -40,6 +40,7 @@ public:
|
||||||
virtual bool wantsKeyboardFocus() const { return false; }
|
virtual bool wantsKeyboardFocus() const { return false; }
|
||||||
virtual void setProxyWindow(QWindow* proxyWindow) {}
|
virtual void setProxyWindow(QWindow* proxyWindow) {}
|
||||||
virtual QObject* getEventHandler() { return nullptr; }
|
virtual QObject* getEventHandler() { return nullptr; }
|
||||||
|
virtual void emitScriptEvent(const QVariant& message) {}
|
||||||
const EntityItemPointer& getEntity() const { return _entity; }
|
const EntityItemPointer& getEntity() const { return _entity; }
|
||||||
const ItemID& getRenderItemID() const { return _renderItemID; }
|
const ItemID& getRenderItemID() const { return _renderItemID; }
|
||||||
|
|
||||||
|
|
|
@ -1034,7 +1034,7 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
locationChanged(false, true);
|
locationChanged(true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ private:
|
||||||
static std::function<void(QSharedPointer<OffscreenQmlSurface>&, bool&, std::vector<QMetaObject::Connection>&)> _releaseWebSurfaceOperator;
|
static std::function<void(QSharedPointer<OffscreenQmlSurface>&, bool&, std::vector<QMetaObject::Connection>&)> _releaseWebSurfaceOperator;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void emitScriptEvent(const QVariant& scriptMessage);
|
void emitScriptEvent(const QVariant& scriptMessage) override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void scriptEventReceived(const QVariant& message);
|
void scriptEventReceived(const QVariant& message);
|
||||||
|
|
|
@ -39,9 +39,7 @@ void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityEditPacketSender::queueEditAvatarEntityMessage(EntityTreePointer entityTree,
|
void EntityEditPacketSender::queueEditAvatarEntityMessage(EntityTreePointer entityTree, EntityItemID entityItemID) {
|
||||||
EntityItemID entityItemID,
|
|
||||||
const EntityItemProperties& properties) {
|
|
||||||
assert(_myAvatar);
|
assert(_myAvatar);
|
||||||
if (!entityTree) {
|
if (!entityTree) {
|
||||||
qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage null entityTree.";
|
qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage null entityTree.";
|
||||||
|
@ -54,11 +52,6 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(EntityTreePointer enti
|
||||||
}
|
}
|
||||||
entity->setLastBroadcast(usecTimestampNow());
|
entity->setLastBroadcast(usecTimestampNow());
|
||||||
|
|
||||||
// serialize ALL properties in an "AvatarEntity" packet
|
|
||||||
// rather than just the ones being edited.
|
|
||||||
EntityItemProperties entityProperties = entity->getProperties();
|
|
||||||
entityProperties.merge(properties);
|
|
||||||
|
|
||||||
OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE);
|
OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE);
|
||||||
EncodeBitstreamParams params;
|
EncodeBitstreamParams params;
|
||||||
EntityTreeElementExtraEncodeDataPointer extra { nullptr };
|
EntityTreeElementExtraEncodeDataPointer extra { nullptr };
|
||||||
|
@ -82,7 +75,7 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
|
||||||
qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit with no myAvatar";
|
qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit with no myAvatar";
|
||||||
} else if (properties.getOwningAvatarID() == _myAvatar->getID()) {
|
} else if (properties.getOwningAvatarID() == _myAvatar->getID()) {
|
||||||
// this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server
|
// this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server
|
||||||
queueEditAvatarEntityMessage(entityTree, entityItemID, properties);
|
queueEditAvatarEntityMessage(entityTree, entityItemID);
|
||||||
} else {
|
} else {
|
||||||
qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit for another avatar";
|
qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit for another avatar";
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,8 @@ public slots:
|
||||||
void processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
void processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void queueEditAvatarEntityMessage(EntityTreePointer entityTree,
|
friend class MyAvatar;
|
||||||
EntityItemID entityItemID, const EntityItemProperties& properties);
|
void queueEditAvatarEntityMessage(EntityTreePointer entityTree, EntityItemID entityItemID);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::mutex _mutex;
|
std::mutex _mutex;
|
||||||
|
|
|
@ -511,8 +511,6 @@ public:
|
||||||
virtual void setProxyWindow(QWindow* proxyWindow) {}
|
virtual void setProxyWindow(QWindow* proxyWindow) {}
|
||||||
virtual QObject* getEventHandler() { return nullptr; }
|
virtual QObject* getEventHandler() { return nullptr; }
|
||||||
|
|
||||||
virtual void emitScriptEvent(const QVariant& message) {}
|
|
||||||
|
|
||||||
QUuid getLastEditedBy() const { return _lastEditedBy; }
|
QUuid getLastEditedBy() const { return _lastEditedBy; }
|
||||||
void setLastEditedBy(QUuid value) { _lastEditedBy = value; }
|
void setLastEditedBy(QUuid value) { _lastEditedBy = value; }
|
||||||
|
|
||||||
|
|
|
@ -2196,14 +2196,7 @@ bool EntityScriptingInterface::wantsHandControllerPointerEvents(const QUuid& id)
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityScriptingInterface::emitScriptEvent(const EntityItemID& entityID, const QVariant& message) {
|
void EntityScriptingInterface::emitScriptEvent(const EntityItemID& entityID, const QVariant& message) {
|
||||||
if (_entityTree) {
|
EntityTree::emitScriptEvent(entityID, message);
|
||||||
_entityTree->withReadLock([&] {
|
|
||||||
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
|
|
||||||
if (entity) {
|
|
||||||
entity->emitScriptEvent(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO move this someplace that makes more sense...
|
// TODO move this someplace that makes more sense...
|
||||||
|
|
|
@ -1529,7 +1529,6 @@ public slots:
|
||||||
* @function Entities.emitScriptEvent
|
* @function Entities.emitScriptEvent
|
||||||
* @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Web} entity.
|
* @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Web} entity.
|
||||||
* @param {string} message - The message to send.
|
* @param {string} message - The message to send.
|
||||||
* @todo <em>This function is currently not implemented.</em>
|
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE void emitScriptEvent(const EntityItemID& entityID, const QVariant& message);
|
Q_INVOKABLE void emitScriptEvent(const EntityItemID& entityID, const QVariant& message);
|
||||||
|
|
||||||
|
|
|
@ -2978,6 +2978,7 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const {
|
||||||
std::function<QObject*(const QUuid&)> EntityTree::_getEntityObjectOperator = nullptr;
|
std::function<QObject*(const QUuid&)> EntityTree::_getEntityObjectOperator = nullptr;
|
||||||
std::function<QSizeF(const QUuid&, const QString&)> EntityTree::_textSizeOperator = nullptr;
|
std::function<QSizeF(const QUuid&, const QString&)> EntityTree::_textSizeOperator = nullptr;
|
||||||
std::function<bool()> EntityTree::_areEntityClicksCapturedOperator = nullptr;
|
std::function<bool()> EntityTree::_areEntityClicksCapturedOperator = nullptr;
|
||||||
|
std::function<void(const QUuid&, const QVariant&)> EntityTree::_emitScriptEventOperator = nullptr;
|
||||||
|
|
||||||
QObject* EntityTree::getEntityObject(const QUuid& id) {
|
QObject* EntityTree::getEntityObject(const QUuid& id) {
|
||||||
if (_getEntityObjectOperator) {
|
if (_getEntityObjectOperator) {
|
||||||
|
@ -3000,6 +3001,12 @@ bool EntityTree::areEntityClicksCaptured() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityTree::emitScriptEvent(const QUuid& id, const QVariant& message) {
|
||||||
|
if (_emitScriptEventOperator) {
|
||||||
|
_emitScriptEventOperator(id, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
|
void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
|
||||||
MovingEntitiesOperator& moveOperator, bool force, bool tellServer) {
|
MovingEntitiesOperator& moveOperator, bool force, bool tellServer) {
|
||||||
// if the queryBox has changed, tell the entity-server
|
// if the queryBox has changed, tell the entity-server
|
||||||
|
|
|
@ -272,6 +272,9 @@ public:
|
||||||
static void setEntityClicksCapturedOperator(std::function<bool()> areEntityClicksCapturedOperator) { _areEntityClicksCapturedOperator = areEntityClicksCapturedOperator; }
|
static void setEntityClicksCapturedOperator(std::function<bool()> areEntityClicksCapturedOperator) { _areEntityClicksCapturedOperator = areEntityClicksCapturedOperator; }
|
||||||
static bool areEntityClicksCaptured();
|
static bool areEntityClicksCaptured();
|
||||||
|
|
||||||
|
static void setEmitScriptEventOperator(std::function<void(const QUuid&, const QVariant&)> emitScriptEventOperator) { _emitScriptEventOperator = emitScriptEventOperator; }
|
||||||
|
static void emitScriptEvent(const QUuid& id, const QVariant& message);
|
||||||
|
|
||||||
std::map<QString, QString> getNamedPaths() const { return _namedPaths; }
|
std::map<QString, QString> getNamedPaths() const { return _namedPaths; }
|
||||||
|
|
||||||
void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
|
void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
|
||||||
|
@ -383,6 +386,7 @@ private:
|
||||||
static std::function<QObject*(const QUuid&)> _getEntityObjectOperator;
|
static std::function<QObject*(const QUuid&)> _getEntityObjectOperator;
|
||||||
static std::function<QSizeF(const QUuid&, const QString&)> _textSizeOperator;
|
static std::function<QSizeF(const QUuid&, const QString&)> _textSizeOperator;
|
||||||
static std::function<bool()> _areEntityClicksCapturedOperator;
|
static std::function<bool()> _areEntityClicksCapturedOperator;
|
||||||
|
static std::function<void(const QUuid&, const QVariant&)> _emitScriptEventOperator;
|
||||||
|
|
||||||
std::vector<int32_t> _staleProxies;
|
std::vector<int32_t> _staleProxies;
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
||||||
#define _texCoord1 _texCoord01.zw
|
#define _texCoord1 _texCoord01.zw
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
vec4 texel = texture(originalTexture, _texCoord0.st);
|
vec4 texel = texture(originalTexture, _texCoord0);
|
||||||
texel = color_sRGBAToLinear(texel);
|
texel = color_sRGBAToLinear(texel);
|
||||||
packDeferredFragmentUnlit(normalize(_normalWS), 1.0, _color.rgb * texel.rgb);
|
packDeferredFragmentUnlit(normalize(_normalWS), 1.0, _color.rgb * texel.rgb);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,11 +28,11 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
||||||
#define _texCoord1 _texCoord01.zw
|
#define _texCoord1 _texCoord01.zw
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
vec4 texel = texture(originalTexture, _texCoord0.st);
|
vec4 texel = texture(originalTexture, _texCoord0);
|
||||||
texel = color_sRGBAToLinear(texel);
|
texel = color_sRGBAToLinear(texel);
|
||||||
packDeferredFragmentTranslucent(
|
packDeferredFragmentTranslucent(
|
||||||
normalize(_normalWS),
|
normalize(_normalWS),
|
||||||
_color.a,
|
_color.a * texel.a,
|
||||||
_color.rgb * texel.rgb,
|
_color.rgb * texel.rgb,
|
||||||
DEFAULT_ROUGHNESS);
|
DEFAULT_ROUGHNESS);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,16 +46,6 @@ ScriptAudioInjector* AudioScriptingInterface::playSystemSound(SharedSoundPointer
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions) {
|
ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions) {
|
||||||
if (QThread::currentThread() != thread()) {
|
|
||||||
ScriptAudioInjector* injector = NULL;
|
|
||||||
|
|
||||||
BLOCKING_INVOKE_METHOD(this, "playSound",
|
|
||||||
Q_RETURN_ARG(ScriptAudioInjector*, injector),
|
|
||||||
Q_ARG(SharedSoundPointer, sound),
|
|
||||||
Q_ARG(const AudioInjectorOptions&, injectorOptions));
|
|
||||||
return injector;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sound) {
|
if (sound) {
|
||||||
// stereo option isn't set from script, this comes from sound metadata or filename
|
// stereo option isn't set from script, this comes from sound metadata or filename
|
||||||
AudioInjectorOptions optionsCopy = injectorOptions;
|
AudioInjectorOptions optionsCopy = injectorOptions;
|
||||||
|
@ -63,7 +53,7 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound
|
||||||
optionsCopy.ambisonic = sound->isAmbisonic();
|
optionsCopy.ambisonic = sound->isAmbisonic();
|
||||||
optionsCopy.localOnly = optionsCopy.localOnly || sound->isAmbisonic(); // force localOnly when Ambisonic
|
optionsCopy.localOnly = optionsCopy.localOnly || sound->isAmbisonic(); // force localOnly when Ambisonic
|
||||||
|
|
||||||
auto injector = AudioInjector::playSound(sound, optionsCopy);
|
auto injector = DependencyManager::get<AudioInjectorManager>()->playSound(sound, optionsCopy);
|
||||||
if (!injector) {
|
if (!injector) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,6 @@ QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* c
|
||||||
return QScriptValue(QScriptValue::NullValue);
|
return QScriptValue(QScriptValue::NullValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the script goes down we want to cleanup the injector
|
|
||||||
QObject::connect(engine, &QScriptEngine::destroyed, in, &ScriptAudioInjector::stopInjectorImmediately,
|
|
||||||
Qt::DirectConnection);
|
|
||||||
|
|
||||||
return engine->newQObject(in, QScriptEngine::ScriptOwnership);
|
return engine->newQObject(in, QScriptEngine::ScriptOwnership);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,13 +33,5 @@ ScriptAudioInjector::ScriptAudioInjector(const AudioInjectorPointer& injector) :
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptAudioInjector::~ScriptAudioInjector() {
|
ScriptAudioInjector::~ScriptAudioInjector() {
|
||||||
if (!_injector.isNull()) {
|
DependencyManager::get<AudioInjectorManager>()->stop(_injector);
|
||||||
// we've been asked to delete after finishing, trigger a queued deleteLater here
|
}
|
||||||
_injector->triggerDeleteAfterFinish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptAudioInjector::stopInjectorImmediately() {
|
|
||||||
qCDebug(scriptengine) << "ScriptAudioInjector::stopInjectorImmediately called to stop audio injector immediately.";
|
|
||||||
_injector->stop();
|
|
||||||
}
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
|
|
||||||
#include <AudioInjector.h>
|
#include <AudioInjectorManager.h>
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Plays — "injects" — the content of an audio file. Used in the {@link Audio} API.
|
* Plays — "injects" — the content of an audio file. Used in the {@link Audio} API.
|
||||||
|
@ -48,7 +48,7 @@ public slots:
|
||||||
* Stop current playback, if any, and start playing from the beginning.
|
* Stop current playback, if any, and start playing from the beginning.
|
||||||
* @function AudioInjector.restart
|
* @function AudioInjector.restart
|
||||||
*/
|
*/
|
||||||
void restart() { _injector->restart(); }
|
void restart() { DependencyManager::get<AudioInjectorManager>()->restart(_injector); }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Stop audio playback.
|
* Stop audio playback.
|
||||||
|
@ -68,28 +68,28 @@ public slots:
|
||||||
* injector.stop();
|
* injector.stop();
|
||||||
* }, 2000);
|
* }, 2000);
|
||||||
*/
|
*/
|
||||||
void stop() { _injector->stop(); }
|
void stop() { DependencyManager::get<AudioInjectorManager>()->stop(_injector); }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Get the current configuration of the audio injector.
|
* Get the current configuration of the audio injector.
|
||||||
* @function AudioInjector.getOptions
|
* @function AudioInjector.getOptions
|
||||||
* @returns {AudioInjector.AudioInjectorOptions} Configuration of how the injector plays the audio.
|
* @returns {AudioInjector.AudioInjectorOptions} Configuration of how the injector plays the audio.
|
||||||
*/
|
*/
|
||||||
const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); }
|
AudioInjectorOptions getOptions() const { return DependencyManager::get<AudioInjectorManager>()->getOptions(_injector); }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Configure how the injector plays the audio.
|
* Configure how the injector plays the audio.
|
||||||
* @function AudioInjector.setOptions
|
* @function AudioInjector.setOptions
|
||||||
* @param {AudioInjector.AudioInjectorOptions} options - Configuration of how the injector plays the audio.
|
* @param {AudioInjector.AudioInjectorOptions} options - Configuration of how the injector plays the audio.
|
||||||
*/
|
*/
|
||||||
void setOptions(const AudioInjectorOptions& options) { _injector->setOptions(options); }
|
void setOptions(const AudioInjectorOptions& options) { DependencyManager::get<AudioInjectorManager>()->setOptions(_injector, options); }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Get the loudness of the most recent frame of audio played.
|
* Get the loudness of the most recent frame of audio played.
|
||||||
* @function AudioInjector.getLoudness
|
* @function AudioInjector.getLoudness
|
||||||
* @returns {number} The loudness of the most recent frame of audio played, range <code>0.0</code> – <code>1.0</code>.
|
* @returns {number} The loudness of the most recent frame of audio played, range <code>0.0</code> – <code>1.0</code>.
|
||||||
*/
|
*/
|
||||||
float getLoudness() const { return _injector->getLoudness(); }
|
float getLoudness() const { return DependencyManager::get<AudioInjectorManager>()->getLoudness(_injector); }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Get whether or not the audio is currently playing.
|
* Get whether or not the audio is currently playing.
|
||||||
|
@ -110,7 +110,7 @@ public slots:
|
||||||
* print("Sound is playing: " + injector.isPlaying());
|
* print("Sound is playing: " + injector.isPlaying());
|
||||||
* }, 2000);
|
* }, 2000);
|
||||||
*/
|
*/
|
||||||
bool isPlaying() const { return _injector->isPlaying(); }
|
bool isPlaying() const { return DependencyManager::get<AudioInjectorManager>()->isPlaying(_injector); }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
|
@ -134,13 +134,6 @@ signals:
|
||||||
*/
|
*/
|
||||||
void finished();
|
void finished();
|
||||||
|
|
||||||
protected slots:
|
|
||||||
|
|
||||||
/**jsdoc
|
|
||||||
* Stop audio playback. (Synonym of {@link AudioInjector.stop|stop}.)
|
|
||||||
* @function AudioInjector.stopInjectorImmediately
|
|
||||||
*/
|
|
||||||
void stopInjectorImmediately();
|
|
||||||
private:
|
private:
|
||||||
AudioInjectorPointer _injector;
|
AudioInjectorPointer _injector;
|
||||||
|
|
||||||
|
|
|
@ -260,12 +260,7 @@ bool ScriptEngine::isDebugMode() const {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptEngine::~ScriptEngine() {
|
ScriptEngine::~ScriptEngine() {}
|
||||||
QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines);
|
|
||||||
if (scriptEngines) {
|
|
||||||
scriptEngines->removeScriptEngine(qSharedPointerCast<ScriptEngine>(sharedFromThis()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::disconnectNonEssentialSignals() {
|
void ScriptEngine::disconnectNonEssentialSignals() {
|
||||||
disconnect();
|
disconnect();
|
||||||
|
|
|
@ -591,6 +591,8 @@ void ScriptEngines::onScriptFinished(const QString& rawScriptURL, ScriptEnginePo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeScriptEngine(engine);
|
||||||
|
|
||||||
if (removed && !_isReloading) {
|
if (removed && !_isReloading) {
|
||||||
// Update settings with removed script
|
// Update settings with removed script
|
||||||
saveScripts();
|
saveScripts();
|
||||||
|
|
|
@ -52,8 +52,15 @@ float UsersScriptingInterface::getAvatarGain(const QUuid& nodeID) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void UsersScriptingInterface::kick(const QUuid& nodeID) {
|
void UsersScriptingInterface::kick(const QUuid& nodeID) {
|
||||||
// ask the NodeList to kick the user with the given session ID
|
|
||||||
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
|
if (_kickConfirmationOperator) {
|
||||||
|
bool waitingForKickResponse = _kickResponseLock.resultWithReadLock<bool>([&] { return _waitingForKickResponse; });
|
||||||
|
if (getCanKick() && !waitingForKickResponse) {
|
||||||
|
_kickConfirmationOperator(nodeID);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UsersScriptingInterface::mute(const QUuid& nodeID) {
|
void UsersScriptingInterface::mute(const QUuid& nodeID) {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#define hifi_UsersScriptingInterface_h
|
#define hifi_UsersScriptingInterface_h
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
|
#include <shared/ReadWriteLockable.h>
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @namespace Users
|
* @namespace Users
|
||||||
|
@ -38,6 +39,12 @@ class UsersScriptingInterface : public QObject, public Dependency {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UsersScriptingInterface();
|
UsersScriptingInterface();
|
||||||
|
void setKickConfirmationOperator(std::function<void(const QUuid& nodeID)> kickConfirmationOperator) {
|
||||||
|
_kickConfirmationOperator = kickConfirmationOperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getWaitForKickResponse() { return _kickResponseLock.resultWithReadLock<bool>([&] { return _waitingForKickResponse; }); }
|
||||||
|
void setWaitForKickResponse(bool waitForKickResponse) { _kickResponseLock.withWriteLock([&] { _waitingForKickResponse = waitForKickResponse; }); }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
|
@ -195,6 +202,11 @@ signals:
|
||||||
private:
|
private:
|
||||||
bool getRequestsDomainListData();
|
bool getRequestsDomainListData();
|
||||||
void setRequestsDomainListData(bool requests);
|
void setRequestsDomainListData(bool requests);
|
||||||
|
|
||||||
|
std::function<void(const QUuid& nodeID)> _kickConfirmationOperator;
|
||||||
|
|
||||||
|
ReadWriteLockable _kickResponseLock;
|
||||||
|
bool _waitingForKickResponse { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,10 @@ public:
|
||||||
_jointIndex(jointIndex) {
|
_jointIndex(jointIndex) {
|
||||||
auto nestablePointer = _spatiallyNestable.lock();
|
auto nestablePointer = _spatiallyNestable.lock();
|
||||||
if (nestablePointer) {
|
if (nestablePointer) {
|
||||||
glm::vec3 nestableDimensions = getActualScale(nestablePointer);
|
if (nestablePointer->getNestableType() != NestableType::Avatar) {
|
||||||
_baseScale = glm::max(glm::vec3(0.001f), nestableDimensions);
|
glm::vec3 nestableDimensions = getActualScale(nestablePointer);
|
||||||
|
_baseScale = glm::max(glm::vec3(0.001f), nestableDimensions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -240,6 +240,12 @@ class MessageBoxListener : public ModalDialogListener {
|
||||||
return static_cast<QMessageBox::StandardButton>(_result.toInt());
|
return static_cast<QMessageBox::StandardButton>(_result.toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
virtual void onDestroyed() override {
|
||||||
|
ModalDialogListener::onDestroyed();
|
||||||
|
onSelected(QMessageBox::NoButton);
|
||||||
|
}
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onSelected(int button) {
|
void onSelected(int button) {
|
||||||
_result = button;
|
_result = button;
|
||||||
|
|
|
@ -34,6 +34,9 @@ class ModalDialogListener : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
friend class OffscreenUi;
|
friend class OffscreenUi;
|
||||||
|
|
||||||
|
public:
|
||||||
|
QQuickItem* getDialogItem() { return _dialog; };
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ModalDialogListener(QQuickItem* dialog);
|
ModalDialogListener(QQuickItem* dialog);
|
||||||
virtual ~ModalDialogListener();
|
virtual ~ModalDialogListener();
|
||||||
|
@ -43,7 +46,7 @@ signals:
|
||||||
void response(const QVariant& value);
|
void response(const QVariant& value);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void onDestroyed();
|
virtual void onDestroyed();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QQuickItem* _dialog;
|
QQuickItem* _dialog;
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
#include "ToolbarScriptingInterface.h"
|
#include "ToolbarScriptingInterface.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
|
|
||||||
#include <AudioInjector.h>
|
#include <AudioInjectorManager.h>
|
||||||
|
|
||||||
#include "SettingHandle.h"
|
#include "SettingHandle.h"
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ void TabletScriptingInterface::playSound(TabletAudioEvents aEvent) {
|
||||||
options.localOnly = true;
|
options.localOnly = true;
|
||||||
options.positionSet = false; // system sound
|
options.positionSet = false; // system sound
|
||||||
|
|
||||||
AudioInjectorPointer injector = AudioInjector::playSoundAndDelete(sound, options);
|
DependencyManager::get<AudioInjectorManager>()->playSound(sound, options, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,6 +368,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
|
||||||
|
|
||||||
if (toolbarMode) {
|
if (toolbarMode) {
|
||||||
#if !defined(DISABLE_QML)
|
#if !defined(DISABLE_QML)
|
||||||
|
closeDialog();
|
||||||
// create new desktop window
|
// create new desktop window
|
||||||
auto tabletRootWindow = new TabletRootWindow();
|
auto tabletRootWindow = new TabletRootWindow();
|
||||||
tabletRootWindow->initQml(QVariantMap());
|
tabletRootWindow->initQml(QVariantMap());
|
||||||
|
|
|
@ -2,12 +2,10 @@
|
||||||
#include "SoundEffect.h"
|
#include "SoundEffect.h"
|
||||||
|
|
||||||
#include <RegisteredMetaTypes.h>
|
#include <RegisteredMetaTypes.h>
|
||||||
#include <AudioInjector.h>
|
|
||||||
|
|
||||||
SoundEffect::~SoundEffect() {
|
SoundEffect::~SoundEffect() {
|
||||||
if (_injector) {
|
if (_injector) {
|
||||||
// stop will cause the AudioInjector to delete itself.
|
DependencyManager::get<AudioInjectorManager>()->stop(_injector);
|
||||||
_injector->stop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,15 +26,14 @@ void SoundEffect::setVolume(float volume) {
|
||||||
_volume = volume;
|
_volume = volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoundEffect::play(QVariant position) {
|
void SoundEffect::play(const QVariant& position) {
|
||||||
AudioInjectorOptions options;
|
AudioInjectorOptions options;
|
||||||
options.position = vec3FromVariant(position);
|
options.position = vec3FromVariant(position);
|
||||||
options.localOnly = true;
|
options.localOnly = true;
|
||||||
options.volume = _volume;
|
options.volume = _volume;
|
||||||
if (_injector) {
|
if (_injector) {
|
||||||
_injector->setOptions(options);
|
DependencyManager::get<AudioInjectorManager>()->setOptionsAndRestart(_injector, options);
|
||||||
_injector->restart();
|
|
||||||
} else {
|
} else {
|
||||||
_injector = AudioInjector::playSound(_sound, options);
|
_injector = DependencyManager::get<AudioInjectorManager>()->playSound(_sound, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,7 @@
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
|
|
||||||
#include <SoundCache.h>
|
#include <SoundCache.h>
|
||||||
|
#include <AudioInjectorManager.h>
|
||||||
class AudioInjector;
|
|
||||||
using AudioInjectorPointer = QSharedPointer<AudioInjector>;
|
|
||||||
|
|
||||||
// SoundEffect object, exposed to qml only, not interface JavaScript.
|
// SoundEffect object, exposed to qml only, not interface JavaScript.
|
||||||
// This is used to play spatial sound effects on tablets/web entities from within QML.
|
// This is used to play spatial sound effects on tablets/web entities from within QML.
|
||||||
|
@ -34,7 +32,7 @@ public:
|
||||||
float getVolume() const;
|
float getVolume() const;
|
||||||
void setVolume(float volume);
|
void setVolume(float volume);
|
||||||
|
|
||||||
Q_INVOKABLE void play(QVariant position);
|
Q_INVOKABLE void play(const QVariant& position);
|
||||||
protected:
|
protected:
|
||||||
QUrl _url;
|
QUrl _url;
|
||||||
float _volume { 1.0f };
|
float _volume { 1.0f };
|
||||||
|
|
|
@ -2524,7 +2524,7 @@ var PropertiesTool = function (opts) {
|
||||||
|
|
||||||
createToolsWindow.webEventReceived.addListener(this, onWebEventReceived);
|
createToolsWindow.webEventReceived.addListener(this, onWebEventReceived);
|
||||||
|
|
||||||
webView.webEventReceived.connect(onWebEventReceived);
|
webView.webEventReceived.connect(this, onWebEventReceived);
|
||||||
|
|
||||||
return that;
|
return that;
|
||||||
};
|
};
|
||||||
|
|
|
@ -203,7 +203,7 @@
|
||||||
// Notification plane positions
|
// Notification plane positions
|
||||||
noticeY = -sensorScaleFactor * (y * NOTIFICATION_3D_SCALE + 0.5 * noticeHeight);
|
noticeY = -sensorScaleFactor * (y * NOTIFICATION_3D_SCALE + 0.5 * noticeHeight);
|
||||||
notificationPosition = { x: 0, y: noticeY, z: 0 };
|
notificationPosition = { x: 0, y: noticeY, z: 0 };
|
||||||
buttonPosition = { x: 0.5 * sensorScaleFactor * (noticeWidth - NOTIFICATION_3D_BUTTON_WIDTH), y: noticeY, z: 0.001 };
|
buttonPosition = { x: sensorScaleFactor * (noticeWidth - NOTIFICATION_3D_BUTTON_WIDTH), y: noticeY, z: 0.001 };
|
||||||
|
|
||||||
// Rotate plane
|
// Rotate plane
|
||||||
notificationOrientation = Quat.fromPitchYawRollDegrees(NOTIFICATIONS_3D_PITCH,
|
notificationOrientation = Quat.fromPitchYawRollDegrees(NOTIFICATIONS_3D_PITCH,
|
||||||
|
@ -241,7 +241,7 @@
|
||||||
noticeWidth = notice.width * NOTIFICATION_3D_SCALE + NOTIFICATION_3D_BUTTON_WIDTH;
|
noticeWidth = notice.width * NOTIFICATION_3D_SCALE + NOTIFICATION_3D_BUTTON_WIDTH;
|
||||||
noticeHeight = notice.height * NOTIFICATION_3D_SCALE;
|
noticeHeight = notice.height * NOTIFICATION_3D_SCALE;
|
||||||
|
|
||||||
notice.size = { x: noticeWidth, y: noticeHeight };
|
notice.size = { x: noticeWidth * sensorScaleFactor, y: noticeHeight * sensorScaleFactor };
|
||||||
|
|
||||||
positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y);
|
positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y);
|
||||||
|
|
||||||
|
@ -249,8 +249,8 @@
|
||||||
notice.parentJointIndex = -2;
|
notice.parentJointIndex = -2;
|
||||||
|
|
||||||
if (!image) {
|
if (!image) {
|
||||||
notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE;
|
notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE * sensorScaleFactor;
|
||||||
notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE;
|
notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE * sensorScaleFactor;
|
||||||
notice.bottomMargin = 0;
|
notice.bottomMargin = 0;
|
||||||
notice.rightMargin = 0;
|
notice.rightMargin = 0;
|
||||||
notice.lineHeight = 10.0 * (fontSize * sensorScaleFactor / 12.0) * NOTIFICATION_3D_SCALE;
|
notice.lineHeight = 10.0 * (fontSize * sensorScaleFactor / 12.0) * NOTIFICATION_3D_SCALE;
|
||||||
|
@ -267,14 +267,15 @@
|
||||||
button.isFacingAvatar = false;
|
button.isFacingAvatar = false;
|
||||||
button.parentID = MyAvatar.sessionUUID;
|
button.parentID = MyAvatar.sessionUUID;
|
||||||
button.parentJointIndex = -2;
|
button.parentJointIndex = -2;
|
||||||
|
button.visible = false;
|
||||||
|
|
||||||
buttons.push((Overlays.addOverlay("image3d", button)));
|
buttons.push((Overlays.addOverlay("image3d", button)));
|
||||||
overlay3DDetails.push({
|
overlay3DDetails.push({
|
||||||
notificationOrientation: positions.notificationOrientation,
|
notificationOrientation: positions.notificationOrientation,
|
||||||
notificationPosition: positions.notificationPosition,
|
notificationPosition: positions.notificationPosition,
|
||||||
buttonPosition: positions.buttonPosition,
|
buttonPosition: positions.buttonPosition,
|
||||||
width: noticeWidth,
|
width: noticeWidth * sensorScaleFactor,
|
||||||
height: noticeHeight
|
height: noticeHeight * sensorScaleFactor
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,8 @@ void ImageComparer::compareImages(const QImage& resultImage, const QImage& expec
|
||||||
|
|
||||||
int windowCounter{ 0 };
|
int windowCounter{ 0 };
|
||||||
double ssim{ 0.0 };
|
double ssim{ 0.0 };
|
||||||
|
double worstTileValue{ 1.0 };
|
||||||
|
|
||||||
double min { 1.0 };
|
double min { 1.0 };
|
||||||
double max { -1.0 };
|
double max { -1.0 };
|
||||||
|
|
||||||
|
@ -108,6 +110,10 @@ void ImageComparer::compareImages(const QImage& resultImage, const QImage& expec
|
||||||
if (value < min) min = value;
|
if (value < min) min = value;
|
||||||
if (value > max) max = value;
|
if (value > max) max = value;
|
||||||
|
|
||||||
|
if (value < worstTileValue) {
|
||||||
|
worstTileValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
++windowCounter;
|
++windowCounter;
|
||||||
|
|
||||||
y += WIN_SIZE;
|
y += WIN_SIZE;
|
||||||
|
@ -122,12 +128,17 @@ void ImageComparer::compareImages(const QImage& resultImage, const QImage& expec
|
||||||
_ssimResults.min = min;
|
_ssimResults.min = min;
|
||||||
_ssimResults.max = max;
|
_ssimResults.max = max;
|
||||||
_ssimResults.ssim = ssim / windowCounter;
|
_ssimResults.ssim = ssim / windowCounter;
|
||||||
|
_ssimResults.worstTileValue = worstTileValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
double ImageComparer::getSSIMValue() {
|
double ImageComparer::getSSIMValue() {
|
||||||
return _ssimResults.ssim;
|
return _ssimResults.ssim;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double ImageComparer::getWorstTileValue() {
|
||||||
|
return _ssimResults.worstTileValue;
|
||||||
|
}
|
||||||
|
|
||||||
SSIMResults ImageComparer::getSSIMResults() {
|
SSIMResults ImageComparer::getSSIMResults() {
|
||||||
return _ssimResults;
|
return _ssimResults;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,9 @@
|
||||||
class ImageComparer {
|
class ImageComparer {
|
||||||
public:
|
public:
|
||||||
void compareImages(const QImage& resultImage, const QImage& expectedImage);
|
void compareImages(const QImage& resultImage, const QImage& expectedImage);
|
||||||
|
|
||||||
double getSSIMValue();
|
double getSSIMValue();
|
||||||
|
double getWorstTileValue();
|
||||||
|
|
||||||
SSIMResults getSSIMResults();
|
SSIMResults getSSIMResults();
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ QPixmap MismatchWindow::computeDiffPixmap(const QImage& expectedImage, const QIm
|
||||||
}
|
}
|
||||||
|
|
||||||
void MismatchWindow::setTestResult(const TestResult& testResult) {
|
void MismatchWindow::setTestResult(const TestResult& testResult) {
|
||||||
errorLabel->setText("Similarity: " + QString::number(testResult._error));
|
errorLabel->setText("Similarity: " + QString::number(testResult._errorGlobal) + " (worst tile: " + QString::number(testResult._errorLocal) + ")");
|
||||||
|
|
||||||
imagePath->setText("Path to test: " + testResult._pathname);
|
imagePath->setText("Path to test: " + testResult._pathname);
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) {
|
||||||
|
|
||||||
_ui.plainTextEdit->setReadOnly(true);
|
_ui.plainTextEdit->setReadOnly(true);
|
||||||
|
|
||||||
setWindowTitle("Nitpick - v3.1.2");
|
setWindowTitle("Nitpick - v3.1.3");
|
||||||
|
|
||||||
clientProfiles << "VR-High" << "Desktop-High" << "Desktop-Low" << "Mobile-Touch" << "VR-Standalone";
|
clientProfiles << "VR-High" << "Desktop-High" << "Desktop-Low" << "Mobile-Touch" << "VR-Standalone";
|
||||||
_ui.clientProfileComboBox->insertItems(0, clientProfiles);
|
_ui.clientProfileComboBox->insertItems(0, clientProfiles);
|
||||||
|
|
|
@ -83,6 +83,7 @@ int TestCreator::compareImageLists() {
|
||||||
QImage expectedImage(_expectedImagesFullFilenames[i]);
|
QImage expectedImage(_expectedImagesFullFilenames[i]);
|
||||||
|
|
||||||
double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical
|
double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical
|
||||||
|
double worstTileValue; // in [-1.0 .. 1.0], where 1.0 means images are identical
|
||||||
|
|
||||||
bool isInteractiveMode = (!_isRunningFromCommandLine && _checkBoxInteractiveMode->isChecked() && !_isRunningInAutomaticTestRun);
|
bool isInteractiveMode = (!_isRunningFromCommandLine && _checkBoxInteractiveMode->isChecked() && !_isRunningInAutomaticTestRun);
|
||||||
|
|
||||||
|
@ -90,13 +91,16 @@ int TestCreator::compareImageLists() {
|
||||||
if (isInteractiveMode && (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height())) {
|
if (isInteractiveMode && (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height())) {
|
||||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size");
|
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size");
|
||||||
similarityIndex = -100.0;
|
similarityIndex = -100.0;
|
||||||
|
worstTileValue = 0.0;
|
||||||
} else {
|
} else {
|
||||||
_imageComparer.compareImages(resultImage, expectedImage);
|
_imageComparer.compareImages(resultImage, expectedImage);
|
||||||
similarityIndex = _imageComparer.getSSIMValue();
|
similarityIndex = _imageComparer.getSSIMValue();
|
||||||
|
worstTileValue = _imageComparer.getWorstTileValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResult testResult = TestResult{
|
TestResult testResult = TestResult{
|
||||||
(float)similarityIndex,
|
similarityIndex,
|
||||||
|
worstTileValue,
|
||||||
_expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /)
|
_expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /)
|
||||||
QFileInfo(_expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image
|
QFileInfo(_expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image
|
||||||
QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of result image
|
QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of result image
|
||||||
|
@ -105,10 +109,9 @@ int TestCreator::compareImageLists() {
|
||||||
|
|
||||||
_mismatchWindow.setTestResult(testResult);
|
_mismatchWindow.setTestResult(testResult);
|
||||||
|
|
||||||
if (similarityIndex < THRESHOLD) {
|
if (similarityIndex < THRESHOLD_GLOBAL || worstTileValue < THRESHOLD_LOCAL) {
|
||||||
++numberOfFailures;
|
|
||||||
|
|
||||||
if (!isInteractiveMode) {
|
if (!isInteractiveMode) {
|
||||||
|
++numberOfFailures;
|
||||||
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), _mismatchWindow.getSSIMResultsImage(testResult._ssimResults), true);
|
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), _mismatchWindow.getSSIMResultsImage(testResult._ssimResults), true);
|
||||||
} else {
|
} else {
|
||||||
_mismatchWindow.exec();
|
_mismatchWindow.exec();
|
||||||
|
@ -117,6 +120,7 @@ int TestCreator::compareImageLists() {
|
||||||
case USER_RESPONSE_PASS:
|
case USER_RESPONSE_PASS:
|
||||||
break;
|
break;
|
||||||
case USE_RESPONSE_FAIL:
|
case USE_RESPONSE_FAIL:
|
||||||
|
++numberOfFailures;
|
||||||
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), _mismatchWindow.getSSIMResultsImage(testResult._ssimResults), true);
|
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), _mismatchWindow.getSSIMResultsImage(testResult._ssimResults), true);
|
||||||
break;
|
break;
|
||||||
case USER_RESPONSE_ABORT:
|
case USER_RESPONSE_ABORT:
|
||||||
|
@ -198,7 +202,8 @@ void TestCreator::appendTestResultsToFile(const TestResult& testResult, const QP
|
||||||
stream << "TestCreator in folder " << testResult._pathname.left(testResult._pathname.length() - 1) << endl; // remove trailing '/'
|
stream << "TestCreator in folder " << testResult._pathname.left(testResult._pathname.length() - 1) << endl; // remove trailing '/'
|
||||||
stream << "Expected image was " << testResult._expectedImageFilename << endl;
|
stream << "Expected image was " << testResult._expectedImageFilename << endl;
|
||||||
stream << "Actual image was " << testResult._actualImageFilename << endl;
|
stream << "Actual image was " << testResult._actualImageFilename << endl;
|
||||||
stream << "Similarity index was " << testResult._error << endl;
|
stream << "Similarity index was " << testResult._errorGlobal << endl;
|
||||||
|
stream << "Worst tile was " << testResult._errorLocal << endl;
|
||||||
|
|
||||||
descriptionFile.close();
|
descriptionFile.close();
|
||||||
|
|
||||||
|
@ -819,6 +824,10 @@ void TestCreator::createRecursiveScript(const QString& directory, bool interacti
|
||||||
|
|
||||||
// If 'directories' is empty, this means that this recursive script has no tests to call, so it is redundant
|
// If 'directories' is empty, this means that this recursive script has no tests to call, so it is redundant
|
||||||
if (directories.length() == 0) {
|
if (directories.length() == 0) {
|
||||||
|
QString testRecursivePathname = directory + "/" + TEST_RECURSIVE_FILENAME;
|
||||||
|
if (QFile::exists(testRecursivePathname)) {
|
||||||
|
QFile::remove(testRecursivePathname);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -851,10 +860,7 @@ void TestCreator::createRecursiveScript(const QString& directory, bool interacti
|
||||||
textStream << " nitpick = createNitpick(Script.resolvePath(\".\"));" << endl;
|
textStream << " nitpick = createNitpick(Script.resolvePath(\".\"));" << endl;
|
||||||
textStream << " testsRootPath = nitpick.getTestsRootPath();" << endl << endl;
|
textStream << " testsRootPath = nitpick.getTestsRootPath();" << endl << endl;
|
||||||
textStream << " nitpick.enableRecursive();" << endl;
|
textStream << " nitpick.enableRecursive();" << endl;
|
||||||
textStream << " nitpick.enableAuto();" << endl << endl;
|
textStream << " nitpick.enableAuto();" << endl;
|
||||||
textStream << " if (typeof Test !== 'undefined') {" << endl;
|
|
||||||
textStream << " Test.wait(10000);" << endl;
|
|
||||||
textStream << " }" << endl;
|
|
||||||
textStream << "} else {" << endl;
|
textStream << "} else {" << endl;
|
||||||
textStream << " depth++" << endl;
|
textStream << " depth++" << endl;
|
||||||
textStream << "}" << endl << endl;
|
textStream << "}" << endl << endl;
|
||||||
|
|
|
@ -121,7 +121,8 @@ private:
|
||||||
const QString TEST_RESULTS_FOLDER { "TestResults" };
|
const QString TEST_RESULTS_FOLDER { "TestResults" };
|
||||||
const QString TEST_RESULTS_FILENAME { "TestResults.txt" };
|
const QString TEST_RESULTS_FILENAME { "TestResults.txt" };
|
||||||
|
|
||||||
const double THRESHOLD{ 0.9999 };
|
const double THRESHOLD_GLOBAL{ 0.9995 };
|
||||||
|
const double THRESHOLD_LOCAL { 0.6 };
|
||||||
|
|
||||||
QDir _imageDirectory;
|
QDir _imageDirectory;
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ TestRunnerMobile::TestRunnerMobile(
|
||||||
_installAPKPushbutton = installAPKPushbutton;
|
_installAPKPushbutton = installAPKPushbutton;
|
||||||
_runInterfacePushbutton = runInterfacePushbutton;
|
_runInterfacePushbutton = runInterfacePushbutton;
|
||||||
|
|
||||||
folderLineEdit->setText("/sdcard/DCIM/TEST");
|
folderLineEdit->setText("/sdcard/snapshots");
|
||||||
|
|
||||||
modelNames["SM_G955U1"] = "Samsung S8+ unlocked";
|
modelNames["SM_G955U1"] = "Samsung S8+ unlocked";
|
||||||
modelNames["SM_N960U1"] = "Samsung Note 9 unlocked";
|
modelNames["SM_N960U1"] = "Samsung Note 9 unlocked";
|
||||||
|
@ -60,6 +60,7 @@ void TestRunnerMobile::setWorkingFolderAndEnableControls() {
|
||||||
setWorkingFolder(_workingFolderLabel);
|
setWorkingFolder(_workingFolderLabel);
|
||||||
|
|
||||||
_connectDeviceButton->setEnabled(true);
|
_connectDeviceButton->setEnabled(true);
|
||||||
|
_downloadAPKPushbutton->setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestRunnerMobile::connectDevice() {
|
void TestRunnerMobile::connectDevice() {
|
||||||
|
@ -154,8 +155,6 @@ void TestRunnerMobile::downloadComplete() {
|
||||||
} else {
|
} else {
|
||||||
_statusLabel->setText("Installer download complete");
|
_statusLabel->setText("Installer download complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
_installAPKPushbutton->setEnabled(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestRunnerMobile::installAPK() {
|
void TestRunnerMobile::installAPK() {
|
||||||
|
@ -164,22 +163,16 @@ void TestRunnerMobile::installAPK() {
|
||||||
_adbInterface = new AdbInterface();
|
_adbInterface = new AdbInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_installerFilename.isNull()) {
|
QString installerPathname = QFileDialog::getOpenFileName(nullptr, "Please select the APK", _workingFolder,
|
||||||
QString installerPathname = QFileDialog::getOpenFileName(nullptr, "Please select the APK", _workingFolder,
|
"Available APKs (*.apk)"
|
||||||
"Available APKs (*.apk)"
|
);
|
||||||
);
|
|
||||||
|
|
||||||
if (installerPathname.isNull()) {
|
if (installerPathname.isNull()) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the path
|
|
||||||
QStringList parts = installerPathname.split('/');
|
|
||||||
_installerFilename = parts[parts.length() - 1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_statusLabel->setText("Installing");
|
_statusLabel->setText("Installing");
|
||||||
QString command = _adbInterface->getAdbCommand() + " install -r -d " + _workingFolder + "/" + _installerFilename + " >" + _workingFolder + "/installOutput.txt";
|
QString command = _adbInterface->getAdbCommand() + " install -r -d " + installerPathname + " >" + _workingFolder + "/installOutput.txt";
|
||||||
appendLog(command);
|
appendLog(command);
|
||||||
system(command.toStdString().c_str());
|
system(command.toStdString().c_str());
|
||||||
_statusLabel->setText("Installation complete");
|
_statusLabel->setText("Installation complete");
|
||||||
|
|
|
@ -18,7 +18,9 @@ public:
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
std::vector<double> results;
|
std::vector<double> results;
|
||||||
|
|
||||||
double ssim;
|
double ssim;
|
||||||
|
double worstTileValue;
|
||||||
|
|
||||||
// Used for scaling
|
// Used for scaling
|
||||||
double min;
|
double min;
|
||||||
|
@ -27,15 +29,17 @@ public:
|
||||||
|
|
||||||
class TestResult {
|
class TestResult {
|
||||||
public:
|
public:
|
||||||
TestResult(float error, const QString& pathname, const QString& expectedImageFilename, const QString& actualImageFilename, const SSIMResults& ssimResults) :
|
TestResult(double errorGlobal, double errorLocal, const QString& pathname, const QString& expectedImageFilename, const QString& actualImageFilename, const SSIMResults& ssimResults) :
|
||||||
_error(error),
|
_errorGlobal(errorGlobal),
|
||||||
|
_errorLocal(errorLocal),
|
||||||
_pathname(pathname),
|
_pathname(pathname),
|
||||||
_expectedImageFilename(expectedImageFilename),
|
_expectedImageFilename(expectedImageFilename),
|
||||||
_actualImageFilename(actualImageFilename),
|
_actualImageFilename(actualImageFilename),
|
||||||
_ssimResults(ssimResults)
|
_ssimResults(ssimResults)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
double _error;
|
double _errorGlobal;
|
||||||
|
double _errorLocal;
|
||||||
|
|
||||||
QString _pathname;
|
QString _pathname;
|
||||||
QString _expectedImageFilename;
|
QString _expectedImageFilename;
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
<widget class="QLabel" name="diffImage">
|
<widget class="QLabel" name="diffImage">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>540</x>
|
<x>900</x>
|
||||||
<y>480</y>
|
<y>480</y>
|
||||||
<width>800</width>
|
<width>800</width>
|
||||||
<height>450</height>
|
<height>450</height>
|
||||||
|
@ -78,7 +78,7 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>60</x>
|
<x>60</x>
|
||||||
<y>630</y>
|
<y>630</y>
|
||||||
<width>480</width>
|
<width>540</width>
|
||||||
<height>28</height>
|
<height>28</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
@ -145,7 +145,7 @@
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Abort current test</string>
|
<string>Abort evaluation</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QLabel" name="errorLabel">
|
<widget class="QLabel" name="errorLabel">
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>5</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="tab_1">
|
<widget class="QWidget" name="tab_1">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
|
|
Loading…
Reference in a new issue