diff --git a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml index 65f8a8c1dc..17d6a7d3b3 100644 --- a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml +++ b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml @@ -379,9 +379,9 @@ Item { Component.onCompleted: { // with the link. if (completeProfileBody.withOculus) { - termsText.text = qsTr("By signing up, you agree to High Fidelity's Terms of Service") + termsText.text = qsTr("By signing up, you agree to High Fidelity's Terms of Service") } else { - termsText.text = qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") + termsText.text = qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") } } } diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml index 64df9089a1..69ac2f5a6c 100644 --- a/interface/resources/qml/LoginDialog/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/SignUpBody.qml @@ -395,7 +395,7 @@ Item { text: signUpBody.termsContainerText Component.onCompleted: { // with the link. - termsText.text = qsTr("By signing up, you agree to High Fidelity's Terms of Service") + termsText.text = qsTr("By signing up, you agree to High Fidelity's Terms of Service") } } diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml index 2c8e61a29a..d450b1e7bc 100644 --- a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml +++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml @@ -218,7 +218,7 @@ Item { text: usernameCollisionBody.termsContainerText Component.onCompleted: { // with the link. - termsText.text = qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") + termsText.text = qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") } } diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 6748418d19..e10f86a947 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -232,6 +232,10 @@ Item { text: "Audio Codec: " + root.audioCodec + " Noise Gate: " + root.audioNoiseGate; } + StatText { + visible: root.expanded; + text: "Injectors (Local/NonLocal): " + root.audioInjectors.x + "/" + root.audioInjectors.y; + } StatText { visible: root.expanded; text: "Entity Servers In: " + root.entityPacketsInKbps + " kbps"; diff --git a/interface/resources/qml/dialogs/TabletMessageBox.qml b/interface/resources/qml/dialogs/TabletMessageBox.qml index 1e6f0734ad..4411651a0f 100644 --- a/interface/resources/qml/dialogs/TabletMessageBox.qml +++ b/interface/resources/qml/dialogs/TabletMessageBox.qml @@ -28,7 +28,7 @@ TabletModalWindow { id: mouse; anchors.fill: parent } - + function click(button) { clickedButton = button; selected(button); diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 8d237d146a..5559c36fd1 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -117,7 +117,6 @@ Rectangle { if (loader.item.hasOwnProperty("gotoPreviousApp")) { loader.item.gotoPreviousApp = true; } - screenChanged("Web", url) }); } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0c46404b39..635932ea1c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1985,6 +1985,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo 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) { auto entities = getEntities(); if (auto entity = entities->renderableForEntityId(id)) { @@ -2342,6 +2349,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return viewFrustum.getPosition(); }); + DependencyManager::get()->setKickConfirmationOperator([this] (const QUuid& nodeID) { userKickConfirmation(nodeID); }); + render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { bool isTablet = url == TabletScriptingInterface::QML; if (htmlContent) { @@ -2704,9 +2713,7 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); - if (_snapshotSoundInjector != nullptr) { - _snapshotSoundInjector->stop(); - } + _snapshotSoundInjector = nullptr; // 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 @@ -3290,6 +3297,40 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) { #endif } +void Application::userKickConfirmation(const QUuid& nodeID) { + auto avatarHashMap = DependencyManager::get(); + 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(answer.toInt()) == QMessageBox::Yes); + // ask the NodeList to kick the user with the given session ID + + if (yes) { + DependencyManager::get()->kickNodeBySessionID(nodeID); + } + + DependencyManager::get()->setWaitForKickResponse(false); + }); + DependencyManager::get()->setWaitForKickResponse(true); + } +} + void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties) { surfaceContext->setContextProperty("Users", DependencyManager::get().data()); surfaceContext->setContextProperty("HMD", DependencyManager::get().data()); @@ -4228,10 +4269,9 @@ void Application::keyPressEvent(QKeyEvent* event) { Setting::Handle notificationSoundSnapshot{ MenuOption::NotificationSoundsSnapshot, true }; if (notificationSounds.get() && notificationSoundSnapshot.get()) { if (_snapshotSoundInjector) { - _snapshotSoundInjector->setOptions(options); - _snapshotSoundInjector->restart(); + DependencyManager::get()->setOptionsAndRestart(_snapshotSoundInjector, options); } else { - _snapshotSoundInjector = AudioInjector::playSound(_snapshotSound, options); + _snapshotSoundInjector = DependencyManager::get()->playSound(_snapshotSound, options); } } takeSnapshot(true); diff --git a/interface/src/Application.h b/interface/src/Application.h index a8cc9450c5..762ac9585a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -593,6 +593,7 @@ private: void toggleTabletUI(bool shouldOpen = false) const; static void setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties); + void userKickConfirmation(const QUuid& nodeID); MainWindow* _window; QElapsedTimer& _sessionRunTimer; diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 5fe35bd23f..54c67daab8 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -149,6 +149,9 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) { emit bookmarkDeleted(bookmarkName); } +void AvatarBookmarks::deleteBookmark() { +} + void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) { auto myAvatar = DependencyManager::get()->getMyAvatar(); auto currentAvatarEntities = myAvatar->getAvatarEntityData(); diff --git a/interface/src/AvatarBookmarks.h b/interface/src/AvatarBookmarks.h index 4623e7d929..df75fec865 100644 --- a/interface/src/AvatarBookmarks.h +++ b/interface/src/AvatarBookmarks.h @@ -76,6 +76,9 @@ protected: void readFromFile() override; QVariantMap getAvatarDataToBookmark(); +protected slots: + void deleteBookmark() override; + private: const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json"; const QString ENTRY_AVATAR_URL = "avatarUrl"; diff --git a/interface/src/Bookmarks.h b/interface/src/Bookmarks.h index 88510e4eda..56d26b55c6 100644 --- a/interface/src/Bookmarks.h +++ b/interface/src/Bookmarks.h @@ -51,13 +51,10 @@ protected: bool _isMenuSorted; protected slots: - /**jsdoc - * @function AvatarBookmarks.deleteBookmark - */ /**jsdoc * @function LocationBookmarks.deleteBookmark */ - void deleteBookmark(); + virtual void deleteBookmark(); private: static bool sortOrder(QAction* a, QAction* b); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c66c0a30cb..69f7054953 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -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. const float AVATAR_STRETCH_FACTOR = 1.0f; - _collisionInjectors.remove_if( - [](const AudioInjectorPointer& injector) { return !injector || injector->isFinished(); }); + _collisionInjectors.remove_if([](const AudioInjectorPointer& injector) { return !injector; }); static const int MAX_INJECTOR_COUNT = 3; if (_collisionInjectors.size() < MAX_INJECTOR_COUNT) { @@ -640,7 +639,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents options.volume = energyFactorOfFull; options.pitch = 1.0f / AVATAR_STRETCH_FACTOR; - auto injector = AudioInjector::playSoundAndDelete(collisionSound, options); + auto injector = DependencyManager::get()->playSound(collisionSound, options, true); _collisionInjectors.emplace_back(injector); } } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 2b58b14d11..0468fbd809 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include // for SetOfEntities @@ -239,7 +239,7 @@ private: std::shared_ptr _myAvatar; quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate. - std::list _collisionInjectors; + std::list> _collisionInjectors; RateCounter<> _myAvatarSendRate; int _numAvatarsUpdated { 0 }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 02ef91cdba..298e661f24 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1570,7 +1570,7 @@ void MyAvatar::handleChangedAvatarEntityData() { entityTree->withWriteLock([&] { EntityItemPointer entity = entityTree->addEntity(id, properties); 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) { - QUuid oldID = getSessionUUID(); + QUuid oldSessionID = getSessionUUID(); Avatar::setSessionUUID(sessionUUID); - QUuid id = getSessionUUID(); - if (id != oldID) { + QUuid newSessionID = getSessionUUID(); + if (DependencyManager::get()->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(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { @@ -3462,15 +3467,20 @@ void MyAvatar::setSessionUUID(const QUuid& sessionUUID) { _avatarEntitiesLock.withReadLock([&] { avatarEntityIDs = _packedAvatarEntityData.keys(); }); + EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); entityTree->withWriteLock([&] { for (const auto& entityID : avatarEntityIDs) { auto entity = entityTree->findEntityByID(entityID); if (!entity) { continue; } - entity->setOwningAvatarID(id); - if (entity->getParentID() == oldID) { - entity->setParentID(id); + entity->setOwningAvatarID(newSessionID); + // NOTE: each attached AvatarEntity should already have the correct updated parentID + // 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(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { entityTree->withWriteLock([&] { // force an update packet EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityID, properties); + packetSender->queueEditAvatarEntityMessage(entityTree, entityID); }); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index aadc8ee268..2f1ff58dcb 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1122,6 +1122,7 @@ public: float getUserEyeHeight() const; virtual SpatialParentTree* getParentTree() const override; + virtual glm::vec3 scaleForChildren() const override { return glm::vec3(getSensorToWorldScale()); } const QUuid& getSelfID() const { return AVATAR_SELF_ID; } @@ -1918,7 +1919,7 @@ private: bool didTeleport(); bool getIsAway() const { return _isAway; } 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::vector _pinnedJoints; diff --git a/interface/src/scripting/TTSScriptingInterface.cpp b/interface/src/scripting/TTSScriptingInterface.cpp index 6589769ece..325e1ff649 100644 --- a/interface/src/scripting/TTSScriptingInterface.cpp +++ b/interface/src/scripting/TTSScriptingInterface.cpp @@ -66,7 +66,7 @@ void TTSScriptingInterface::updateLastSoundAudioInjector() { if (_lastSoundAudioInjector) { AudioInjectorOptions options; options.position = DependencyManager::get()->getMyAvatarPosition(); - _lastSoundAudioInjector->setOptions(options); + DependencyManager::get()->setOptions(_lastSoundAudioInjector, options); _lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS); } } @@ -143,7 +143,7 @@ void TTSScriptingInterface::speakText(const QString& textToSpeak) { options.position = DependencyManager::get()->getMyAvatarPosition(); if (_lastSoundAudioInjector) { - _lastSoundAudioInjector->stop(); + DependencyManager::get()->stop(_lastSoundAudioInjector); _lastSoundAudioInjectorUpdateTimer.stop(); } @@ -151,7 +151,7 @@ void TTSScriptingInterface::speakText(const QString& textToSpeak) { uint32_t numSamples = (uint32_t)_lastSoundByteArray.size() / sizeof(AudioData::AudioSample); auto samples = reinterpret_cast(_lastSoundByteArray.data()); auto newAudioData = AudioData::make(numSamples, numChannels, samples); - _lastSoundAudioInjector = AudioInjector::playSoundAndDelete(newAudioData, options); + _lastSoundAudioInjector = DependencyManager::get()->playSound(newAudioData, options, true); _lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS); #else @@ -161,7 +161,7 @@ void TTSScriptingInterface::speakText(const QString& textToSpeak) { void TTSScriptingInterface::stopLastSpeech() { if (_lastSoundAudioInjector) { - _lastSoundAudioInjector->stop(); - _lastSoundAudioInjector = NULL; + DependencyManager::get()->stop(_lastSoundAudioInjector); + _lastSoundAudioInjector = nullptr; } } diff --git a/interface/src/ui/Keyboard.cpp b/interface/src/ui/Keyboard.cpp index 9b75f78e67..1cbe31f1eb 100644 --- a/interface/src/ui/Keyboard.cpp +++ b/interface/src/ui/Keyboard.cpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include @@ -537,7 +537,7 @@ void Keyboard::handleTriggerBegin(const QUuid& id, const PointerEvent& event) { audioOptions.position = keyWorldPosition; audioOptions.volume = 0.05f; - AudioInjector::playSoundAndDelete(_keySound, audioOptions); + DependencyManager::get()->playSound(_keySound, audioOptions, true); int scanCode = key.getScanCode(_capsEnabled); QString keyString = key.getKeyString(_capsEnabled); diff --git a/interface/src/ui/Keyboard.h b/interface/src/ui/Keyboard.h index b3358e486d..51e5e0571f 100644 --- a/interface/src/ui/Keyboard.h +++ b/interface/src/ui/Keyboard.h @@ -19,9 +19,9 @@ #include #include #include +#include #include #include -#include #include #include diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index ecdae0b375..022b57c0d9 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -266,6 +266,11 @@ void Stats::updateStats(bool force) { } STAT_UPDATE(audioCodec, audioClient->getSelectedAudioFormat()); STAT_UPDATE(audioNoiseGate, audioClient->getNoiseGateOpen() ? "Open" : "Closed"); + { + int localInjectors = audioClient->getNumLocalInjectors(); + size_t nonLocalInjectors = DependencyManager::get()->getNumInjectors(); + STAT_UPDATE(audioInjectors, QVector2D(localInjectors, nonLocalInjectors)); + } STAT_UPDATE(entityPacketsInKbps, octreeServerCount ? totalEntityKbps / octreeServerCount : -1); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 0f563a6935..3134b223d6 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -87,6 +87,7 @@ private: \ * @property {number} audioPacketLoss - Read-only. * @property {string} audioCodec - Read-only. * @property {string} audioNoiseGate - Read-only. + * @property {Vec2} audioInjectors - Read-only. * @property {number} entityPacketsInKbps - Read-only. * * @property {number} downloads - Read-only. @@ -243,6 +244,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, audioPacketLoss, 0) STATS_PROPERTY(QString, audioCodec, QString()) STATS_PROPERTY(QString, audioNoiseGate, QString()) + STATS_PROPERTY(QVector2D, audioInjectors, QVector2D()); STATS_PROPERTY(int, entityPacketsInKbps, 0) STATS_PROPERTY(int, downloads, 0) @@ -692,6 +694,13 @@ signals: */ void audioNoiseGateChanged(); + /**jsdoc + * Triggered when the value of the audioInjectors property changes. + * @function Stats.audioInjectorsChanged + * @returns {Signal} + */ + void audioInjectorsChanged(); + /**jsdoc * Triggered when the value of the entityPacketsInKbps property changes. * @function Stats.entityPacketsInKbpsChanged diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 1c10d24f23..9d645a1dbf 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1354,26 +1354,28 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { for (const AudioInjectorPointer& injector : _activeLocalAudioInjectors) { // the lock guarantees that injectorBuffer, if found, is invariant - AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer(); + auto injectorBuffer = injector->getLocalBuffer(); if (injectorBuffer) { + auto options = injector->getOptions(); + 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; // get one frame from the injector memset(_localScratchBuffer, 0, 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 - glm::vec3 relativePosition = injector->getPosition() - _positionGetter(); + glm::vec3 relativePosition = options.position - _positionGetter(); float distance = glm::max(glm::length(relativePosition), EPSILON); gain = gainForSource(distance, gain); } @@ -1382,7 +1384,7 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { // Calculate the soundfield orientation relative to the listener. // 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 float qw = relativeOrientation.w; @@ -1394,12 +1396,12 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { injector->getLocalFOA().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX, 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 - glm::vec3 relativePosition = injector->getPosition() - _positionGetter(); + glm::vec3 relativePosition = options.position - _positionGetter(); float distance = glm::max(glm::length(relativePosition), EPSILON); gain = gainForSource(distance, gain); } @@ -1412,10 +1414,10 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { } else { // injector is mono - if (injector->isPositionSet()) { + if (options.positionSet) { // distance attenuation - glm::vec3 relativePosition = injector->getPosition() - _positionGetter(); + glm::vec3 relativePosition = options.position - _positionGetter(); float distance = glm::max(glm::length(relativePosition), EPSILON); gain = gainForSource(distance, gain); @@ -1437,21 +1439,21 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { } 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(); injectorsToRemove.append(injector); } } 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(); injectorsToRemove.append(injector); } } for (const AudioInjectorPointer& injector : injectorsToRemove) { - qCDebug(audioclient) << "removing injector"; + //qCDebug(audioclient) << "removing injector"; _activeLocalAudioInjectors.removeOne(injector); } @@ -1571,15 +1573,13 @@ bool AudioClient::setIsStereoInput(bool isStereoInput) { } bool AudioClient::outputLocalInjector(const AudioInjectorPointer& injector) { - AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer(); + auto injectorBuffer = injector->getLocalBuffer(); if (injectorBuffer) { // local injectors are on the AudioInjectorsThread, so we must guard access Lock lock(_injectorsMutex); if (!_activeLocalAudioInjectors.contains(injector)) { - qCDebug(audioclient) << "adding new injector"; + //qCDebug(audioclient) << "adding new injector"; _activeLocalAudioInjectors.append(injector); - // move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop()) - injectorBuffer->setParent(nullptr); // update the flag _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() { _outputFrameSize = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * OUTPUT_CHANNEL_COUNT * _outputFormat.sampleRate()) / _desiredOutputFormat.sampleRate(); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index b9648219a5..a153f22bf3 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -181,6 +181,8 @@ public: bool isHeadsetPluggedIn() { return _isHeadsetPluggedIn; } #endif + int getNumLocalInjectors(); + public slots: void start(); void stop(); diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 1581990e0c..4911917bf0 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -24,9 +24,10 @@ #include "AudioRingBuffer.h" #include "AudioLogging.h" #include "SoundCache.h" -#include "AudioSRC.h" #include "AudioHelpers.h" +int metaType = qRegisterMetaType("AudioInjectorPointer"); + AbstractAudioInterface* AudioInjector::_localAudioInterface{ nullptr }; AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs) { @@ -51,26 +52,30 @@ AudioInjector::AudioInjector(AudioDataPointer audioData, const AudioInjectorOpti { } -AudioInjector::~AudioInjector() { - deleteLocalBuffer(); -} +AudioInjector::~AudioInjector() {} bool AudioInjector::stateHas(AudioInjectorState state) const { - return (_state & state) == state; + return resultWithReadLock([&] { + return (_state & state) == state; + }); } void AudioInjector::setOptions(const AudioInjectorOptions& options) { // since options.stereo is computed from the audio stream, // we need to copy it from existing options just in case. - bool currentlyStereo = _options.stereo; - bool currentlyAmbisonic = _options.ambisonic; - _options = options; - _options.stereo = currentlyStereo; - _options.ambisonic = currentlyAmbisonic; + withWriteLock([&] { + bool currentlyStereo = _options.stereo; + bool currentlyAmbisonic = _options.ambisonic; + _options = options; + _options.stereo = currentlyStereo; + _options.ambisonic = currentlyAmbisonic; + }); } void AudioInjector::finishNetworkInjection() { - _state |= AudioInjectorState::NetworkInjectionFinished; + withWriteLock([&] { + _state |= AudioInjectorState::NetworkInjectionFinished; + }); // if we are already finished with local // injection, then we are finished @@ -80,35 +85,31 @@ void AudioInjector::finishNetworkInjection() { } void AudioInjector::finishLocalInjection() { - _state |= AudioInjectorState::LocalInjectionFinished; - if(_options.localOnly || stateHas(AudioInjectorState::NetworkInjectionFinished)) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "finishLocalInjection"); + return; + } + + bool localOnly = false; + withWriteLock([&] { + _state |= AudioInjectorState::LocalInjectionFinished; + localOnly = _options.localOnly; + }); + + if(localOnly || stateHas(AudioInjectorState::NetworkInjectionFinished)) { finish(); } } void AudioInjector::finish() { - _state |= AudioInjectorState::Finished; - + withWriteLock([&] { + _state |= AudioInjectorState::Finished; + }); emit finished(); - - deleteLocalBuffer(); + _localBuffer = nullptr; } void AudioInjector::restart() { - // grab the AudioInjectorManager - auto injectorManager = DependencyManager::get(); - - 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 _currentSendOffset = 0; @@ -121,19 +122,23 @@ void AudioInjector::restart() { // check our state to decide if we need extra handling for the restart request if (stateHas(AudioInjectorState::Finished)) { - if (!inject(&AudioInjectorManager::restartFinishedInjector)) { + if (!inject(&AudioInjectorManager::threadInjector)) { qWarning() << "AudioInjector::restart failed to thread injector"; } } } bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&)) { - _state = AudioInjectorState::NotFinished; + AudioInjectorOptions options; + withWriteLock([&] { + _state = AudioInjectorState::NotFinished; + options = _options; + }); int byteOffset = 0; - if (_options.secondOffset > 0.0f) { - int numChannels = _options.ambisonic ? 4 : (_options.stereo ? 2 : 1); - byteOffset = (int)(AudioConstants::SAMPLE_RATE * _options.secondOffset * numChannels); + if (options.secondOffset > 0.0f) { + int numChannels = options.ambisonic ? 4 : (options.stereo ? 2 : 1); + byteOffset = (int)(AudioConstants::SAMPLE_RATE * options.secondOffset * numChannels); byteOffset *= AudioConstants::SAMPLE_SIZE; } _currentSendOffset = byteOffset; @@ -143,7 +148,7 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInj } bool success = true; - if (!_options.localOnly) { + if (!options.localOnly) { auto injectorManager = DependencyManager::get(); if (!(*injectorManager.*injection)(sharedFromThis())) { success = false; @@ -158,7 +163,8 @@ bool AudioInjector::injectLocally() { if (_localAudioInterface) { if (_audioData->getNumBytes() > 0) { - _localBuffer = new AudioInjectorLocalBuffer(_audioData); + _localBuffer = QSharedPointer(new AudioInjectorLocalBuffer(_audioData), &AudioInjectorLocalBuffer::deleteLater); + _localBuffer->moveToThread(thread()); _localBuffer->open(QIODevice::ReadOnly); _localBuffer->setShouldLoop(_options.loop); @@ -181,14 +187,6 @@ bool AudioInjector::injectLocally() { return success; } -void AudioInjector::deleteLocalBuffer() { - if (_localBuffer) { - _localBuffer->stop(); - _localBuffer->deleteLater(); - _localBuffer = nullptr; - } -} - 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_IMMEDIATELY = 0; @@ -220,6 +218,10 @@ int64_t AudioInjector::injectNextFrame() { static int volumeOptionOffset = -1; static int audioDataOffset = -1; + AudioInjectorOptions options = resultWithReadLock([&] { + return _options; + }); + if (!_currentPacket) { if (_currentSendOffset < 0 || _currentSendOffset >= (int)_audioData->getNumBytes()) { @@ -253,7 +255,7 @@ int64_t AudioInjector::injectNextFrame() { audioPacketStream << QUuid::createUuid(); // pack the stereo/mono type of the stream - audioPacketStream << _options.stereo; + audioPacketStream << options.stereo; // pack the flag for loopback, if requested loopbackOptionOffset = _currentPacket->pos(); @@ -262,15 +264,16 @@ int64_t AudioInjector::injectNextFrame() { // pack the position for injected audio positionOptionOffset = _currentPacket->pos(); - audioPacketStream.writeRawData(reinterpret_cast(&_options.position), - sizeof(_options.position)); + audioPacketStream.writeRawData(reinterpret_cast(&options.position), + sizeof(options.position)); // pack our orientation for injected audio - audioPacketStream.writeRawData(reinterpret_cast(&_options.orientation), - sizeof(_options.orientation)); + audioPacketStream.writeRawData(reinterpret_cast(&options.orientation), + sizeof(options.orientation)); + + audioPacketStream.writeRawData(reinterpret_cast(&options.position), + sizeof(options.position)); - audioPacketStream.writeRawData(reinterpret_cast(&_options.position), - sizeof(_options.position)); glm::vec3 boxCorner = glm::vec3(0); audioPacketStream.writeRawData(reinterpret_cast(&boxCorner), sizeof(glm::vec3)); @@ -283,7 +286,7 @@ int64_t AudioInjector::injectNextFrame() { volumeOptionOffset = _currentPacket->pos(); quint8 volume = MAX_INJECTOR_VOLUME; audioPacketStream << volume; - audioPacketStream << _options.ignorePenumbra; + audioPacketStream << options.ignorePenumbra; audioDataOffset = _currentPacket->pos(); @@ -313,10 +316,10 @@ int64_t AudioInjector::injectNextFrame() { _currentPacket->writePrimitive((uchar)(_localAudioInterface && _localAudioInterface->shouldLoopbackInjectors())); _currentPacket->seek(positionOptionOffset); - _currentPacket->writePrimitive(_options.position); - _currentPacket->writePrimitive(_options.orientation); + _currentPacket->writePrimitive(options.position); + _currentPacket->writePrimitive(options.orientation); - quint8 volume = packFloatGainToByte(_options.volume); + quint8 volume = packFloatGainToByte(options.volume); _currentPacket->seek(volumeOptionOffset); _currentPacket->writePrimitive(volume); @@ -326,8 +329,8 @@ int64_t AudioInjector::injectNextFrame() { // Might be a reasonable place to do the encode step here. QByteArray decodedAudio; - int totalBytesLeftToCopy = (_options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; - if (!_options.loop) { + int totalBytesLeftToCopy = (options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; + if (!options.loop) { // If we aren't looping, let's make sure we don't read past the end int bytesLeftToRead = _audioData->getNumBytes() - _currentSendOffset; totalBytesLeftToCopy = std::min(totalBytesLeftToCopy, bytesLeftToRead); @@ -342,14 +345,16 @@ int64_t AudioInjector::injectNextFrame() { auto samplesOut = reinterpret_cast(decodedAudio.data()); // Copy and Measure the loudness of this frame - _loudness = 0.0f; - for (int i = 0; i < samplesLeftToCopy; ++i) { - auto index = (currentSample + i) % _audioData->getNumSamples(); - auto sample = samples[index]; - samplesOut[i] = sample; - _loudness += abs(sample) / (AudioConstants::MAX_SAMPLE_VALUE / 2.0f); - } - _loudness /= (float)samplesLeftToCopy; + withWriteLock([&] { + _loudness = 0.0f; + for (int i = 0; i < samplesLeftToCopy; ++i) { + auto index = (currentSample + i) % _audioData->getNumSamples(); + auto sample = samples[index]; + samplesOut[i] = sample; + _loudness += abs(sample) / (AudioConstants::MAX_SAMPLE_VALUE / 2.0f); + } + _loudness /= (float)samplesLeftToCopy; + }); _currentSendOffset = (_currentSendOffset + totalBytesLeftToCopy) % _audioData->getNumBytes(); @@ -371,7 +376,7 @@ int64_t AudioInjector::injectNextFrame() { _outgoingSequenceNumber++; } - if (_currentSendOffset == 0 && !_options.loop) { + if (_currentSendOffset == 0 && !options.loop) { finishNetworkInjection(); 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 qCDebug(audio) << this << "injectNextFrame() skipping ahead, fell behind by " << (currentFrameBasedOnElapsedTime - _nextFrame) << " frames"; _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; 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(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(resampledBuffer.data()); - - resampler.render(audioData->data(), bufferPtr, numFrames); - - int numSamples = maxOutputFrames * numChannels; - auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr); - - return AudioInjector::playSound(newAudioData, options); - } } \ No newline at end of file diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 3c21d2eccf..555af84025 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -19,6 +19,8 @@ #include #include +#include + #include #include @@ -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 // until it dies. -class AudioInjector : public QObject, public QEnableSharedFromThis { +class AudioInjector : public QObject, public QEnableSharedFromThis, public ReadWriteLockable { Q_OBJECT public: AudioInjector(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions); @@ -61,40 +63,34 @@ public: int getCurrentSendOffset() const { return _currentSendOffset; } void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; } - AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; } + QSharedPointer getLocalBuffer() const { return _localBuffer; } AudioHRTF& getLocalHRTF() { return _localHRTF; } AudioFOA& getLocalFOA() { return _localFOA; } - bool isLocalOnly() const { return _options.localOnly; } - float getVolume() const { return _options.volume; } - bool isPositionSet() const { return _options.positionSet; } - glm::vec3 getPosition() const { return _options.position; } - glm::quat getOrientation() const { return _options.orientation; } - bool isStereo() const { return _options.stereo; } - bool isAmbisonic() const { return _options.ambisonic; } + float getLoudness() const { return resultWithReadLock([&] { return _loudness; }); } + bool isPlaying() const { return !stateHas(AudioInjectorState::Finished); } + + bool isLocalOnly() const { return resultWithReadLock([&] { return _options.localOnly; }); } + float getVolume() const { return resultWithReadLock([&] { return _options.volume; }); } + bool isPositionSet() const { return resultWithReadLock([&] { return _options.positionSet; }); } + glm::vec3 getPosition() const { return resultWithReadLock([&] { return _options.position; }); } + glm::quat getOrientation() const { return resultWithReadLock([&] { return _options.orientation; }); } + bool isStereo() const { return resultWithReadLock([&] { return _options.stereo; }); } + bool isAmbisonic() const { return resultWithReadLock([&] { return _options.ambisonic; }); } + + AudioInjectorOptions getOptions() const { return resultWithReadLock([&] { return _options; }); } + void setOptions(const AudioInjectorOptions& options); bool stateHas(AudioInjectorState state) const ; static void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; } - static AudioInjectorPointer playSoundAndDelete(SharedSoundPointer sound, const AudioInjectorOptions& options); - static AudioInjectorPointer playSound(SharedSoundPointer sound, const AudioInjectorOptions& options); - static AudioInjectorPointer playSoundAndDelete(AudioDataPointer audioData, const AudioInjectorOptions& options); - static AudioInjectorPointer playSound(AudioDataPointer audioData, const AudioInjectorOptions& options); + void restart(); + void finish(); + + void finishNetworkInjection(); 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 finishNetworkInjection(); signals: void finished(); @@ -104,7 +100,6 @@ private: int64_t injectNextFrame(); bool inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&)); bool injectLocally(); - void deleteLocalBuffer(); static AbstractAudioInterface* _localAudioInterface; @@ -116,7 +111,7 @@ private: float _loudness { 0.0f }; int _currentSendOffset { 0 }; std::unique_ptr _currentPacket { nullptr }; - AudioInjectorLocalBuffer* _localBuffer { nullptr }; + QSharedPointer _localBuffer { nullptr }; int64_t _nextFrame { 0 }; std::unique_ptr _frameTimer { nullptr }; @@ -128,4 +123,6 @@ private: friend class AudioInjectorManager; }; +Q_DECLARE_METATYPE(AudioInjectorPointer) + #endif // hifi_AudioInjector_h diff --git a/libraries/audio/src/AudioInjectorLocalBuffer.cpp b/libraries/audio/src/AudioInjectorLocalBuffer.cpp index 015d87e03b..680513abf5 100644 --- a/libraries/audio/src/AudioInjectorLocalBuffer.cpp +++ b/libraries/audio/src/AudioInjectorLocalBuffer.cpp @@ -16,6 +16,10 @@ AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(AudioDataPointer audioData) : { } +AudioInjectorLocalBuffer::~AudioInjectorLocalBuffer() { + stop(); +} + void AudioInjectorLocalBuffer::stop() { _isStopped = true; @@ -30,9 +34,8 @@ bool AudioInjectorLocalBuffer::seek(qint64 pos) { } } - qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) { - if (!_isStopped) { + if (!_isStopped && _audioData) { // first copy to the end of the raw audio int bytesToEnd = (int)_audioData->getNumBytes() - _currentOffset; diff --git a/libraries/audio/src/AudioInjectorLocalBuffer.h b/libraries/audio/src/AudioInjectorLocalBuffer.h index e0f8847883..2f73e5b313 100644 --- a/libraries/audio/src/AudioInjectorLocalBuffer.h +++ b/libraries/audio/src/AudioInjectorLocalBuffer.h @@ -22,6 +22,7 @@ class AudioInjectorLocalBuffer : public QIODevice { Q_OBJECT public: AudioInjectorLocalBuffer(AudioDataPointer audioData); + ~AudioInjectorLocalBuffer(); void stop(); diff --git a/libraries/audio/src/AudioInjectorManager.cpp b/libraries/audio/src/AudioInjectorManager.cpp index f30d3093ec..e5ffc77798 100644 --- a/libraries/audio/src/AudioInjectorManager.cpp +++ b/libraries/audio/src/AudioInjectorManager.cpp @@ -14,11 +14,14 @@ #include #include +#include #include "AudioConstants.h" #include "AudioInjector.h" #include "AudioLogging.h" +#include "AudioSRC.h" + AudioInjectorManager::~AudioInjectorManager() { _shouldStop = true; @@ -30,7 +33,7 @@ AudioInjectorManager::~AudioInjectorManager() { auto& timePointerPair = _injectors.top(); // ask it to stop and be deleted - timePointerPair.second->stop(); + timePointerPair.second->finish(); _injectors.pop(); } @@ -46,6 +49,8 @@ AudioInjectorManager::~AudioInjectorManager() { _thread->quit(); _thread->wait(); } + + moveToThread(qApp->thread()); } 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 connect(_thread, &QThread::started, this, &AudioInjectorManager::run, Qt::DirectConnection); + moveToThread(_thread); + // start the thread _thread->start(); } @@ -141,36 +148,7 @@ bool AudioInjectorManager::wouldExceedLimits() { // Should be called inside of a bool AudioInjectorManager::threadInjector(const AudioInjectorPointer& injector) { if (_shouldStop) { - 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."; + qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down."; return false; } @@ -188,3 +166,192 @@ bool AudioInjectorManager::restartFinishedInjector(const AudioInjectorPointer& i } 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(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(resampledBuffer.data()); + + resampler.render(audioData->data(), bufferPtr, numFrames); + + int numSamples = maxOutputFrames * numChannels; + auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr); + + injector = QSharedPointer(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(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(resampledBuffer.data()); + + resampler.render(audioData->data(), bufferPtr, numFrames); + + int numSamples = maxOutputFrames * numChannels; + auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr); + + injector = QSharedPointer(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(); +} \ No newline at end of file diff --git a/libraries/audio/src/AudioInjectorManager.h b/libraries/audio/src/AudioInjectorManager.h index 9aca3014e3..cb243f23de 100644 --- a/libraries/audio/src/AudioInjectorManager.h +++ b/libraries/audio/src/AudioInjectorManager.h @@ -30,8 +30,27 @@ class AudioInjectorManager : public QObject, public Dependency { SINGLETON_DEPENDENCY public: ~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: void run(); + private: using TimeInjectorPointerPair = std::pair; @@ -49,11 +68,10 @@ private: using Lock = std::unique_lock; bool threadInjector(const AudioInjectorPointer& injector); - bool restartFinishedInjector(const AudioInjectorPointer& injector); void notifyInjectorReadyCondition() { _injectorReady.notify_one(); } bool wouldExceedLimits(); - AudioInjectorManager() {}; + AudioInjectorManager() { createThread(); } AudioInjectorManager(const AudioInjectorManager&) = delete; AudioInjectorManager& operator=(const AudioInjectorManager&) = delete; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 38108416ee..992ee5db96 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -376,7 +376,7 @@ bool Avatar::applyGrabChanges() { const EntityItemPointer& entity = std::dynamic_pointer_cast(target); if (entity && entity->getEntityHostType() == entity::HostType::AVATAR && entity->getSimulationOwner().getID() == getID()) { EntityItemProperties properties = entity->getProperties(); - sendPacket(entity->getID(), properties); + sendPacket(entity->getID()); } } } else { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index d81b04d4b2..6026367440 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -605,7 +605,7 @@ protected: // protected methods... bool isLookingAtMe(AvatarSharedPointer avatar) const; - virtual void sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const { } + virtual void sendPacket(const QUuid& entityID) const { } bool applyGrabChanges(); void relayJointDataToChildren(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 143c7fa377..c235460404 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1105,7 +1105,7 @@ void EntityTreeRenderer::playEntityCollisionSound(const EntityItemPointer& entit options.volume = volume; options.pitch = 1.0f / stretchFactor; - AudioInjector::playSoundAndDelete(collisionSound, options); + DependencyManager::get()->playSound(collisionSound, options, true); } void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index a257951ba8..a511d73210 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -16,7 +16,7 @@ #include #include -#include +#include #include // for RayToEntityIntersectionResult #include #include diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index e9a6035e3d..39f9ad091e 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -40,6 +40,7 @@ public: virtual bool wantsKeyboardFocus() const { return false; } virtual void setProxyWindow(QWindow* proxyWindow) {} virtual QObject* getEventHandler() { return nullptr; } + virtual void emitScriptEvent(const QVariant& message) {} const EntityItemPointer& getEntity() const { return _entity; } const ItemID& getRenderItemID() const { return _renderItemID; } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 0345898b62..7118774d30 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -106,7 +106,7 @@ private: static std::function&, bool&, std::vector&)> _releaseWebSurfaceOperator; public slots: - void emitScriptEvent(const QVariant& scriptMessage); + void emitScriptEvent(const QVariant& scriptMessage) override; signals: void scriptEventReceived(const QVariant& message); diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index af0e34303b..0491bdedae 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -39,9 +39,7 @@ void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByte } } -void EntityEditPacketSender::queueEditAvatarEntityMessage(EntityTreePointer entityTree, - EntityItemID entityItemID, - const EntityItemProperties& properties) { +void EntityEditPacketSender::queueEditAvatarEntityMessage(EntityTreePointer entityTree, EntityItemID entityItemID) { assert(_myAvatar); if (!entityTree) { qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage null entityTree."; @@ -54,11 +52,6 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(EntityTreePointer enti } 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); EncodeBitstreamParams params; 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"; } else if (properties.getOwningAvatarID() == _myAvatar->getID()) { // 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 { qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit for another avatar"; } diff --git a/libraries/entities/src/EntityEditPacketSender.h b/libraries/entities/src/EntityEditPacketSender.h index 99a5202986..3cc2f016f0 100644 --- a/libraries/entities/src/EntityEditPacketSender.h +++ b/libraries/entities/src/EntityEditPacketSender.h @@ -50,8 +50,8 @@ public slots: void processEntityEditNackPacket(QSharedPointer message, SharedNodePointer sendingNode); private: - void queueEditAvatarEntityMessage(EntityTreePointer entityTree, - EntityItemID entityItemID, const EntityItemProperties& properties); + friend class MyAvatar; + void queueEditAvatarEntityMessage(EntityTreePointer entityTree, EntityItemID entityItemID); private: std::mutex _mutex; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index a9a8baa413..fae871a124 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -511,8 +511,6 @@ public: virtual void setProxyWindow(QWindow* proxyWindow) {} virtual QObject* getEventHandler() { return nullptr; } - virtual void emitScriptEvent(const QVariant& message) {} - QUuid getLastEditedBy() const { return _lastEditedBy; } void setLastEditedBy(QUuid value) { _lastEditedBy = value; } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 6610439183..aa4b3902c2 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -2196,14 +2196,7 @@ bool EntityScriptingInterface::wantsHandControllerPointerEvents(const QUuid& id) } void EntityScriptingInterface::emitScriptEvent(const EntityItemID& entityID, const QVariant& message) { - if (_entityTree) { - _entityTree->withReadLock([&] { - EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); - if (entity) { - entity->emitScriptEvent(message); - } - }); - } + EntityTree::emitScriptEvent(entityID, message); } // TODO move this someplace that makes more sense... diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 0cf9070b08..f6aedac3fc 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -1529,7 +1529,6 @@ public slots: * @function Entities.emitScriptEvent * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|Web} entity. * @param {string} message - The message to send. - * @todo This function is currently not implemented. */ Q_INVOKABLE void emitScriptEvent(const EntityItemID& entityID, const QVariant& message); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index d64c8870eb..8bf7c92b1f 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2978,6 +2978,7 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const { std::function EntityTree::_getEntityObjectOperator = nullptr; std::function EntityTree::_textSizeOperator = nullptr; std::function EntityTree::_areEntityClicksCapturedOperator = nullptr; +std::function EntityTree::_emitScriptEventOperator = nullptr; QObject* EntityTree::getEntityObject(const QUuid& id) { if (_getEntityObjectOperator) { @@ -3000,6 +3001,12 @@ bool EntityTree::areEntityClicksCaptured() { return false; } +void EntityTree::emitScriptEvent(const QUuid& id, const QVariant& message) { + if (_emitScriptEventOperator) { + _emitScriptEventOperator(id, message); + } +} + void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, MovingEntitiesOperator& moveOperator, bool force, bool tellServer) { // if the queryBox has changed, tell the entity-server diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 39b3dc57c7..e627a07d13 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -272,6 +272,9 @@ public: static void setEntityClicksCapturedOperator(std::function areEntityClicksCapturedOperator) { _areEntityClicksCapturedOperator = areEntityClicksCapturedOperator; } static bool areEntityClicksCaptured(); + static void setEmitScriptEventOperator(std::function emitScriptEventOperator) { _emitScriptEventOperator = emitScriptEventOperator; } + static void emitScriptEvent(const QUuid& id, const QVariant& message); + std::map getNamedPaths() const { return _namedPaths; } void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, @@ -383,6 +386,7 @@ private: static std::function _getEntityObjectOperator; static std::function _textSizeOperator; static std::function _areEntityClicksCapturedOperator; + static std::function _emitScriptEventOperator; std::vector _staleProxies; diff --git a/libraries/render-utils/src/simple_opaque_web_browser.slf b/libraries/render-utils/src/simple_opaque_web_browser.slf index 36b0c825ad..df789ee22b 100644 --- a/libraries/render-utils/src/simple_opaque_web_browser.slf +++ b/libraries/render-utils/src/simple_opaque_web_browser.slf @@ -28,7 +28,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; #define _texCoord1 _texCoord01.zw void main(void) { - vec4 texel = texture(originalTexture, _texCoord0.st); + vec4 texel = texture(originalTexture, _texCoord0); texel = color_sRGBAToLinear(texel); packDeferredFragmentUnlit(normalize(_normalWS), 1.0, _color.rgb * texel.rgb); } diff --git a/libraries/render-utils/src/simple_transparent_web_browser.slf b/libraries/render-utils/src/simple_transparent_web_browser.slf index 1d5aad0914..599fd3d87f 100644 --- a/libraries/render-utils/src/simple_transparent_web_browser.slf +++ b/libraries/render-utils/src/simple_transparent_web_browser.slf @@ -28,11 +28,11 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; #define _texCoord1 _texCoord01.zw void main(void) { - vec4 texel = texture(originalTexture, _texCoord0.st); + vec4 texel = texture(originalTexture, _texCoord0); texel = color_sRGBAToLinear(texel); packDeferredFragmentTranslucent( normalize(_normalWS), - _color.a, + _color.a * texel.a, _color.rgb * texel.rgb, DEFAULT_ROUGHNESS); } diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index 65d71e46e6..a55cac292f 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -46,16 +46,6 @@ ScriptAudioInjector* AudioScriptingInterface::playSystemSound(SharedSoundPointer } 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) { // stereo option isn't set from script, this comes from sound metadata or filename AudioInjectorOptions optionsCopy = injectorOptions; @@ -63,7 +53,7 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound optionsCopy.ambisonic = sound->isAmbisonic(); optionsCopy.localOnly = optionsCopy.localOnly || sound->isAmbisonic(); // force localOnly when Ambisonic - auto injector = AudioInjector::playSound(sound, optionsCopy); + auto injector = DependencyManager::get()->playSound(sound, optionsCopy); if (!injector) { return nullptr; } diff --git a/libraries/script-engine/src/ScriptAudioInjector.cpp b/libraries/script-engine/src/ScriptAudioInjector.cpp index 8b51377bff..267ac3339d 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.cpp +++ b/libraries/script-engine/src/ScriptAudioInjector.cpp @@ -19,10 +19,6 @@ QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* c 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); } @@ -37,13 +33,5 @@ ScriptAudioInjector::ScriptAudioInjector(const AudioInjectorPointer& injector) : } ScriptAudioInjector::~ScriptAudioInjector() { - if (!_injector.isNull()) { - // 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(); -} + DependencyManager::get()->stop(_injector); +} \ No newline at end of file diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h index c7fb2f8a9a..d77291b92c 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/script-engine/src/ScriptAudioInjector.h @@ -14,7 +14,7 @@ #include -#include +#include /**jsdoc * 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. * @function AudioInjector.restart */ - void restart() { _injector->restart(); } + void restart() { DependencyManager::get()->restart(_injector); } /**jsdoc * Stop audio playback. @@ -68,28 +68,28 @@ public slots: * injector.stop(); * }, 2000); */ - void stop() { _injector->stop(); } + void stop() { DependencyManager::get()->stop(_injector); } /**jsdoc * Get the current configuration of the audio injector. * @function AudioInjector.getOptions * @returns {AudioInjector.AudioInjectorOptions} Configuration of how the injector plays the audio. */ - const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); } + AudioInjectorOptions getOptions() const { return DependencyManager::get()->getOptions(_injector); } /**jsdoc * Configure how the injector plays the audio. * @function AudioInjector.setOptions * @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()->setOptions(_injector, options); } /**jsdoc * Get the loudness of the most recent frame of audio played. * @function AudioInjector.getLoudness * @returns {number} The loudness of the most recent frame of audio played, range 0.01.0. */ - float getLoudness() const { return _injector->getLoudness(); } + float getLoudness() const { return DependencyManager::get()->getLoudness(_injector); } /**jsdoc * Get whether or not the audio is currently playing. @@ -110,7 +110,7 @@ public slots: * print("Sound is playing: " + injector.isPlaying()); * }, 2000); */ - bool isPlaying() const { return _injector->isPlaying(); } + bool isPlaying() const { return DependencyManager::get()->isPlaying(_injector); } signals: @@ -134,13 +134,6 @@ signals: */ void finished(); -protected slots: - - /**jsdoc - * Stop audio playback. (Synonym of {@link AudioInjector.stop|stop}.) - * @function AudioInjector.stopInjectorImmediately - */ - void stopInjectorImmediately(); private: AudioInjectorPointer _injector; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 825017b1fe..a4fd2540d4 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -260,12 +260,7 @@ bool ScriptEngine::isDebugMode() const { #endif } -ScriptEngine::~ScriptEngine() { - QSharedPointer scriptEngines(_scriptEngines); - if (scriptEngines) { - scriptEngines->removeScriptEngine(qSharedPointerCast(sharedFromThis())); - } -} +ScriptEngine::~ScriptEngine() {} void ScriptEngine::disconnectNonEssentialSignals() { disconnect(); diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 3963ad5593..25c330e3fe 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -591,6 +591,8 @@ void ScriptEngines::onScriptFinished(const QString& rawScriptURL, ScriptEnginePo } } + removeScriptEngine(engine); + if (removed && !_isReloading) { // Update settings with removed script saveScripts(); diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp index fef11c12e9..9beb52f20a 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.cpp +++ b/libraries/script-engine/src/UsersScriptingInterface.cpp @@ -52,8 +52,15 @@ float UsersScriptingInterface::getAvatarGain(const QUuid& nodeID) { } void UsersScriptingInterface::kick(const QUuid& nodeID) { - // ask the NodeList to kick the user with the given session ID - DependencyManager::get()->kickNodeBySessionID(nodeID); + + if (_kickConfirmationOperator) { + bool waitingForKickResponse = _kickResponseLock.resultWithReadLock([&] { return _waitingForKickResponse; }); + if (getCanKick() && !waitingForKickResponse) { + _kickConfirmationOperator(nodeID); + } + } else { + DependencyManager::get()->kickNodeBySessionID(nodeID); + } } void UsersScriptingInterface::mute(const QUuid& nodeID) { diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 57de205066..f8ca974b8b 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -15,6 +15,7 @@ #define hifi_UsersScriptingInterface_h #include +#include /**jsdoc * @namespace Users @@ -38,6 +39,12 @@ class UsersScriptingInterface : public QObject, public Dependency { public: UsersScriptingInterface(); + void setKickConfirmationOperator(std::function kickConfirmationOperator) { + _kickConfirmationOperator = kickConfirmationOperator; + } + + bool getWaitForKickResponse() { return _kickResponseLock.resultWithReadLock([&] { return _waitingForKickResponse; }); } + void setWaitForKickResponse(bool waitForKickResponse) { _kickResponseLock.withWriteLock([&] { _waitingForKickResponse = waitForKickResponse; }); } public slots: @@ -195,6 +202,11 @@ signals: private: bool getRequestsDomainListData(); void setRequestsDomainListData(bool requests); + + std::function _kickConfirmationOperator; + + ReadWriteLockable _kickResponseLock; + bool _waitingForKickResponse { false }; }; diff --git a/libraries/shared/src/NestableTransformNode.h b/libraries/shared/src/NestableTransformNode.h index a584bcd308..f70d158c91 100644 --- a/libraries/shared/src/NestableTransformNode.h +++ b/libraries/shared/src/NestableTransformNode.h @@ -20,8 +20,10 @@ public: _jointIndex(jointIndex) { auto nestablePointer = _spatiallyNestable.lock(); if (nestablePointer) { - glm::vec3 nestableDimensions = getActualScale(nestablePointer); - _baseScale = glm::max(glm::vec3(0.001f), nestableDimensions); + if (nestablePointer->getNestableType() != NestableType::Avatar) { + glm::vec3 nestableDimensions = getActualScale(nestablePointer); + _baseScale = glm::max(glm::vec3(0.001f), nestableDimensions); + } } } diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 137cffde94..2f2d38fe2a 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -240,6 +240,12 @@ class MessageBoxListener : public ModalDialogListener { return static_cast(_result.toInt()); } +protected slots: + virtual void onDestroyed() override { + ModalDialogListener::onDestroyed(); + onSelected(QMessageBox::NoButton); + } + private slots: void onSelected(int button) { _result = button; diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index 46dbdbdf13..6abbc486d0 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -34,6 +34,9 @@ class ModalDialogListener : public QObject { Q_OBJECT friend class OffscreenUi; +public: + QQuickItem* getDialogItem() { return _dialog; }; + protected: ModalDialogListener(QQuickItem* dialog); virtual ~ModalDialogListener(); @@ -43,7 +46,7 @@ signals: void response(const QVariant& value); protected slots: - void onDestroyed(); + virtual void onDestroyed(); protected: QQuickItem* _dialog; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 7a1c37af33..b8895b6714 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -23,7 +23,7 @@ #include "ToolbarScriptingInterface.h" #include "Logging.h" -#include +#include #include "SettingHandle.h" @@ -212,7 +212,7 @@ void TabletScriptingInterface::playSound(TabletAudioEvents aEvent) { options.localOnly = true; options.positionSet = false; // system sound - AudioInjectorPointer injector = AudioInjector::playSoundAndDelete(sound, options); + DependencyManager::get()->playSound(sound, options, true); } } @@ -368,6 +368,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { if (toolbarMode) { #if !defined(DISABLE_QML) + closeDialog(); // create new desktop window auto tabletRootWindow = new TabletRootWindow(); tabletRootWindow->initQml(QVariantMap()); diff --git a/libraries/ui/src/ui/types/SoundEffect.cpp b/libraries/ui/src/ui/types/SoundEffect.cpp index dc2328b33e..38114ecef1 100644 --- a/libraries/ui/src/ui/types/SoundEffect.cpp +++ b/libraries/ui/src/ui/types/SoundEffect.cpp @@ -2,12 +2,10 @@ #include "SoundEffect.h" #include -#include SoundEffect::~SoundEffect() { if (_injector) { - // stop will cause the AudioInjector to delete itself. - _injector->stop(); + DependencyManager::get()->stop(_injector); } } @@ -28,15 +26,14 @@ void SoundEffect::setVolume(float volume) { _volume = volume; } -void SoundEffect::play(QVariant position) { +void SoundEffect::play(const QVariant& position) { AudioInjectorOptions options; options.position = vec3FromVariant(position); options.localOnly = true; options.volume = _volume; if (_injector) { - _injector->setOptions(options); - _injector->restart(); + DependencyManager::get()->setOptionsAndRestart(_injector, options); } else { - _injector = AudioInjector::playSound(_sound, options); + _injector = DependencyManager::get()->playSound(_sound, options); } } diff --git a/libraries/ui/src/ui/types/SoundEffect.h b/libraries/ui/src/ui/types/SoundEffect.h index a7e29d86f9..cb8a5cd67f 100644 --- a/libraries/ui/src/ui/types/SoundEffect.h +++ b/libraries/ui/src/ui/types/SoundEffect.h @@ -13,9 +13,7 @@ #include #include - -class AudioInjector; -using AudioInjectorPointer = QSharedPointer; +#include // SoundEffect object, exposed to qml only, not interface JavaScript. // This is used to play spatial sound effects on tablets/web entities from within QML. @@ -34,7 +32,7 @@ public: float getVolume() const; void setVolume(float volume); - Q_INVOKABLE void play(QVariant position); + Q_INVOKABLE void play(const QVariant& position); protected: QUrl _url; float _volume { 1.0f }; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index ca2918a108..894ea2b696 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2524,7 +2524,7 @@ var PropertiesTool = function (opts) { createToolsWindow.webEventReceived.addListener(this, onWebEventReceived); - webView.webEventReceived.connect(onWebEventReceived); + webView.webEventReceived.connect(this, onWebEventReceived); return that; }; diff --git a/tools/jsdoc/hifi-jsdoc-template/tmpl/signalList.tmpl b/tools/jsdoc/hifi-jsdoc-template/tmpl/signalList.tmpl index b9a0e0ca86..c5fdefc7d8 100644 --- a/tools/jsdoc/hifi-jsdoc-template/tmpl/signalList.tmpl +++ b/tools/jsdoc/hifi-jsdoc-template/tmpl/signalList.tmpl @@ -13,8 +13,8 @@ var self = this; - - + + diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index b4350ddbdb..b2b91de1c8 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -121,23 +121,14 @@ exports.handlers = { e.doclet.description = (e.doclet.description ? e.doclet.description : "") + availableIn; } } + + if (e.doclet.kind === "function" && e.doclet.returns && e.doclet.returns[0].type + && e.doclet.returns[0].type.names[0] === "Signal") { + e.doclet.kind = "signal"; + } } }; -// Functions for adding @signal custom tag -/** @private */ -function setDocletKindToTitle(doclet, tag) { - doclet.addTag( 'kind', tag.title ); -} - -function setDocletNameToValue(doclet, tag) { - if (tag.value && tag.value.description) { // as in a long tag - doclet.addTag('name', tag.value.description); - } else if (tag.text) { // or a short tag - doclet.addTag('name', tag.text); - } -} - // Define custom hifi tags here exports.defineTags = function (dictionary) { @@ -168,14 +159,5 @@ exports.defineTags = function (dictionary) { doclet.hifiServerEntity = true; } }); - - // @signal - dictionary.defineTag("signal", { - mustHaveValue: true, - onTagged: function(doclet, tag) { - setDocletKindToTitle(doclet, tag); - setDocletNameToValue(doclet, tag); - } - }); -}; \ No newline at end of file +}; diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter/AvatarExporter.cs index c5bc5eb84e..87f401d478 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter/AvatarExporter.cs @@ -17,7 +17,7 @@ using System.Text.RegularExpressions; class AvatarExporter : MonoBehaviour { // update version number for every PR that changes this file, also set updated version in README file - static readonly string AVATAR_EXPORTER_VERSION = "0.3.5"; + static readonly string AVATAR_EXPORTER_VERSION = "0.3.6"; static readonly float HIPS_GROUND_MIN_Y = 0.01f; static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f; @@ -56,6 +56,8 @@ class AvatarExporter : MonoBehaviour { "2018.1.0f2", "2017.4.18f1", "2017.4.17f1", + "2017.4.16f1", + "2017.4.15f1", }; static readonly Dictionary HUMANOID_TO_HIFI_JOINT_NAME = new Dictionary { @@ -1159,8 +1161,7 @@ class AvatarExporter : MonoBehaviour { static string GetMaterialTexture(Material material, string textureProperty) { // ensure the texture property name exists in this material and return its texture directory path if so - string[] textureNames = material.GetTexturePropertyNames(); - if (Array.IndexOf(textureNames, textureProperty) >= 0) { + if (material.HasProperty(textureProperty)) { Texture texture = material.GetTexture(textureProperty); if (texture) { foreach (var textureDependency in textureDependencies) { @@ -1214,10 +1215,10 @@ class AvatarExporter : MonoBehaviour { } if (unsupportedShaderMaterials.Count > 1) { warnings += "The materials " + unsupportedShaders + " are using an unsupported shader. " + - "Please change them to a Standard shader type.\n\n"; + "We recommend you change them to a Standard shader type.\n\n"; } else if (unsupportedShaderMaterials.Count == 1) { warnings += "The material " + unsupportedShaders + " is using an unsupported shader. " + - "Please change it to a Standard shader type.\n\n"; + "We recommend you change it to a Standard shader type.\n\n"; } } diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt index 767c093800..5b228ebf75 100644 --- a/tools/unity-avatar-exporter/Assets/README.txt +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -1,6 +1,6 @@ High Fidelity, Inc. Avatar Exporter -Version 0.3.5 +Version 0.3.6 Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter. diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 3e2d6f2aed..05ad49baa6 100644 Binary files a/tools/unity-avatar-exporter/avatarExporter.unitypackage and b/tools/unity-avatar-exporter/avatarExporter.unitypackage differ