diff --git a/interface/resources/html/createGlobalEventBridge.js b/interface/resources/html/createGlobalEventBridge.js
index 4a0de464c3..bccbdfaf7c 100644
--- a/interface/resources/html/createGlobalEventBridge.js
+++ b/interface/resources/html/createGlobalEventBridge.js
@@ -33,6 +33,54 @@ var EventBridge;
// replace the TempEventBridge with the real one.
var tempEventBridge = EventBridge;
EventBridge = channel.objects.eventBridge;
+ EventBridge.audioOutputDeviceChanged.connect(function(deviceName) {
+ navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then(function(mediaStream) {
+ navigator.mediaDevices.enumerateDevices().then(function(devices) {
+ devices.forEach(function(device) {
+ if (device.kind == "audiooutput") {
+ if (device.label == deviceName){
+ console.log("Changing HTML audio output to device " + device.label);
+ var deviceId = device.deviceId;
+ var videos = document.getElementsByTagName("video");
+ for (var i = 0; i < videos.length; i++){
+ videos[i].setSinkId(deviceId);
+ }
+ var audios = document.getElementsByTagName("audio");
+ for (var i = 0; i < audios.length; i++){
+ audios[i].setSinkId(deviceId);
+ }
+ }
+ }
+ });
+
+ }).catch(function(err) {
+ console.log("Error getting media devices"+ err.name + ": " + err.message);
+ });
+ }).catch(function(err) {
+ console.log("Error getting user media"+ err.name + ": " + err.message);
+ });
+ });
+
+ // To be able to update the state of the output device selection for every element added to the DOM
+ // we need to listen to events that might precede the addition of this elements.
+ // A more robust hack will be to add a setInterval that look for DOM changes every 100-300 ms (low performance?)
+
+ window.onload = function(){
+ setTimeout(function() {
+ EventBridge.forceHtmlAudioOutputDeviceUpdate();
+ }, 1200);
+ };
+ document.onclick = function(){
+ setTimeout(function() {
+ EventBridge.forceHtmlAudioOutputDeviceUpdate();
+ }, 1200);
+ };
+ document.onchange = function(){
+ setTimeout(function() {
+ EventBridge.forceHtmlAudioOutputDeviceUpdate();
+ }, 1200);
+ };
+
tempEventBridge._callbacks.forEach(function (callback) {
EventBridge.scriptEventReceived.connect(callback);
});
diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml
index 2809f91923..f1dd3c8350 100644
--- a/interface/resources/qml/Browser.qml
+++ b/interface/resources/qml/Browser.qml
@@ -212,7 +212,7 @@ ScrollingWindow {
WebEngineScript {
id: createGlobalEventBridge
sourceCode: eventBridgeJavaScriptToInject
- injectionPoint: WebEngineScript.DocumentCreation
+ injectionPoint: WebEngineScript.Deferred
worldId: WebEngineScript.MainWorld
}
@@ -233,9 +233,13 @@ ScrollingWindow {
anchors.right: parent.right
onFeaturePermissionRequested: {
- permissionsBar.securityOrigin = securityOrigin;
- permissionsBar.feature = feature;
- root.showPermissionsBar();
+ if (feature == 2) { // QWebEnginePage::MediaAudioCapture
+ grantFeaturePermission(securityOrigin, feature, true);
+ } else {
+ permissionsBar.securityOrigin = securityOrigin;
+ permissionsBar.feature = feature;
+ root.showPermissionsBar();
+ }
}
onLoadingChanged: {
diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt
index f28157ff97..ae682a11ea 100644
--- a/libraries/ui/CMakeLists.txt
+++ b/libraries/ui/CMakeLists.txt
@@ -1,6 +1,6 @@
set(TARGET_NAME ui)
-setup_hifi_library(OpenGL Network Qml Quick Script WebChannel WebEngine WebSockets XmlPatterns)
-link_hifi_libraries(shared networking gl audio)
+setup_hifi_library(OpenGL Multimedia Network Qml Quick Script WebChannel WebEngine WebSockets XmlPatterns)
+link_hifi_libraries(shared networking gl audio audio-client plugins)
# Required for some low level GL interaction in the OffscreenQMLSurface
target_opengl()
diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp
index 01f8f4580a..26a190e105 100644
--- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp
+++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp
@@ -24,7 +24,11 @@
#include
#include
#include
+#include
+#include
+#include
+#include
#include
#include
#include
@@ -113,6 +117,69 @@ uint64_t uvec2ToUint64(const uvec2& v) {
return result;
}
+// Class to handle changing QML audio output device using another thread
+class AudioHandler : public QObject, QRunnable {
+ Q_OBJECT
+public:
+ AudioHandler(QObject* container, const QString& deviceName, int runDelayMs = 0, QObject* parent = nullptr) : QObject(parent) {
+ _container = container;
+ _newTargetDevice = deviceName;
+ _runDelayMs = runDelayMs;
+ setAutoDelete(true);
+ QThreadPool::globalInstance()->start(this);
+ }
+ virtual ~AudioHandler() {
+ qDebug() << "Audio Handler Destroyed";
+ }
+ void run() override {
+ if (_newTargetDevice.isEmpty()) {
+ return;
+ }
+ if (_runDelayMs > 0) {
+ QThread::msleep(_runDelayMs);
+ }
+ auto audioIO = DependencyManager::get();
+ QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName();
+ for (auto player : _container->findChildren()) {
+ auto mediaState = player->state();
+ QMediaService *svc = player->service();
+ if (nullptr == svc) {
+ return;
+ }
+ QAudioOutputSelectorControl *out = qobject_cast
+ (svc->requestControl(QAudioOutputSelectorControl_iid));
+ if (nullptr == out) {
+ return;
+ }
+ QString deviceOuput;
+ auto outputs = out->availableOutputs();
+ for (int i = 0; i < outputs.size(); i++) {
+ QString output = outputs[i];
+ QString description = out->outputDescription(output);
+ if (description == deviceName) {
+ deviceOuput = output;
+ break;
+ }
+ }
+ out->setActiveOutput(deviceOuput);
+ svc->releaseControl(out);
+ // if multimedia was paused, it will start playing automatically after changing audio device
+ // this will reset it back to a paused state
+ if (mediaState == QMediaPlayer::State::PausedState) {
+ player->pause();
+ } else if (mediaState == QMediaPlayer::State::StoppedState) {
+ player->stop();
+ }
+ }
+ qDebug() << "QML Audio changed to " << deviceName;
+ }
+
+private:
+ QString _newTargetDevice;
+ QObject* _container;
+ int _runDelayMs;
+};
+
class OffscreenTextures {
public:
GLuint getNextTexture(const uvec2& size) {
@@ -595,6 +662,14 @@ void OffscreenQmlSurface::create() {
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
_qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, _qmlContext));
_renderControl->initialize(_canvas->getContext());
+
+ // Connect with the audio client and listen for audio device changes
+ auto audioIO = DependencyManager::get();
+ connect(audioIO.data(), &AudioClient::deviceChanged, this, [&](QAudio::Mode mode, const QAudioDeviceInfo& device) {
+ if (mode == QAudio::Mode::AudioOutput) {
+ QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::QueuedConnection, Q_ARG(QString, device.deviceName()));
+ }
+ });
// When Quick says there is a need to render, we will not render immediately. Instead,
// a timer with a small interval is used to get better performance.
@@ -605,6 +680,32 @@ void OffscreenQmlSurface::create() {
_updateTimer.start();
}
+void OffscreenQmlSurface::changeAudioOutputDevice(const QString& deviceName, bool isHtmlUpdate) {
+ if (_rootItem != nullptr && !isHtmlUpdate) {
+ QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection);
+ }
+ emit audioOutputDeviceChanged(deviceName);
+}
+
+void OffscreenQmlSurface::forceHtmlAudioOutputDeviceUpdate() {
+ auto audioIO = DependencyManager::get();
+ QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName();
+ QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::QueuedConnection,
+ Q_ARG(QString, deviceName), Q_ARG(bool, true));
+}
+
+void OffscreenQmlSurface::forceQmlAudioOutputDeviceUpdate() {
+ if (QThread::currentThread() != qApp->thread()) {
+ QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection);
+ } else {
+ auto audioIO = DependencyManager::get();
+ QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName();
+ int waitForAudioQmlMs = 500;
+ // The audio device need to be change using oth
+ new AudioHandler(_rootItem, deviceName, waitForAudioQmlMs);
+ }
+}
+
static uvec2 clampSize(const uvec2& size, uint32_t maxDimension) {
return glm::clamp(size, glm::uvec2(1), glm::uvec2(maxDimension));
}
@@ -798,6 +899,7 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
if (newItem) {
newItem->setParentItem(_rootItem);
}
+ QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection);
return;
}
@@ -817,6 +919,7 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
for (const auto& callback : callbacks) {
callback(qmlContext, newObject);
}
+ QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection);
}
void OffscreenQmlSurface::updateQuick() {
diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h
index 5acdeb4f40..eaff2bc3d1 100644
--- a/libraries/ui/src/ui/OffscreenQmlSurface.h
+++ b/libraries/ui/src/ui/OffscreenQmlSurface.h
@@ -104,6 +104,14 @@ public slots:
void onAboutToQuit();
void focusDestroyed(QObject *obj);
+ // audio output device
+public slots:
+ void changeAudioOutputDevice(const QString& deviceName, bool isHtmlUpdate = false);
+ void forceHtmlAudioOutputDeviceUpdate();
+ void forceQmlAudioOutputDeviceUpdate();
+signals:
+ void audioOutputDeviceChanged(const QString& deviceName);
+
// event bridge
public slots:
void emitScriptEvent(const QVariant& scriptMessage);
diff --git a/plugins/hifiSixense/CMakeLists.txt b/plugins/hifiSixense/CMakeLists.txt
index 55880584a8..6e642fce29 100644
--- a/plugins/hifiSixense/CMakeLists.txt
+++ b/plugins/hifiSixense/CMakeLists.txt
@@ -15,4 +15,7 @@ if (NOT ANDROID)
setup_hifi_plugin(Script Qml Widgets)
link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins)
target_sixense()
+ if (WIN32)
+ target_link_libraries(${TARGET_NAME} Winmm.lib)
+ endif()
endif ()
diff --git a/tests/controllers/CMakeLists.txt b/tests/controllers/CMakeLists.txt
index 3221070837..c3d25cfe2e 100644
--- a/tests/controllers/CMakeLists.txt
+++ b/tests/controllers/CMakeLists.txt
@@ -17,5 +17,9 @@ if (WIN32)
target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES})
endif()
+if (WIN32)
+ target_link_libraries(${TARGET_NAME} Winmm.lib)
+ add_dependency_external_projects(wasapi)
+endif()
package_libraries_for_deployment()
diff --git a/tests/entities/CMakeLists.txt b/tests/entities/CMakeLists.txt
index 080ae7cdd9..0c33eb8555 100644
--- a/tests/entities/CMakeLists.txt
+++ b/tests/entities/CMakeLists.txt
@@ -9,4 +9,8 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
# link in the shared libraries
link_hifi_libraries(entities avatars shared octree gpu model fbx networking animation audio gl)
+if (WIN32)
+ add_dependency_external_projects(wasapi)
+endif ()
+
package_libraries_for_deployment()
diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt
index d73d7a111d..8e49d523b8 100644
--- a/tests/gpu-test/CMakeLists.txt
+++ b/tests/gpu-test/CMakeLists.txt
@@ -5,6 +5,11 @@ setup_hifi_project(Quick Gui OpenGL Script Widgets)
setup_memory_debugger()
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
link_hifi_libraries(networking gl gpu gpu-gl procedural shared fbx model model-networking animation script-engine render render-utils octree image ktx)
+
+if (WIN32)
+ add_dependency_external_projects(wasapi)
+endif ()
+
package_libraries_for_deployment()
target_nsight()
diff --git a/tests/qt59/CMakeLists.txt b/tests/qt59/CMakeLists.txt
index e0e8138a1e..e3450ae069 100644
--- a/tests/qt59/CMakeLists.txt
+++ b/tests/qt59/CMakeLists.txt
@@ -11,7 +11,12 @@ setup_memory_debugger()
setup_hifi_project(Gui)
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
+
# link in the shared libraries
link_hifi_libraries(shared networking)
+if (WIN32)
+ add_dependency_external_projects(wasapi)
+endif()
+
package_libraries_for_deployment()
diff --git a/tests/recording/CMakeLists.txt b/tests/recording/CMakeLists.txt
index b5b1e6a54e..dbb942a27a 100644
--- a/tests/recording/CMakeLists.txt
+++ b/tests/recording/CMakeLists.txt
@@ -4,6 +4,10 @@ setup_hifi_project(Test)
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
setup_memory_debugger()
link_hifi_libraries(shared recording)
+if (WIN32)
+ target_link_libraries(${TARGET_NAME} Winmm.lib)
+ add_dependency_external_projects(wasapi)
+endif()
package_libraries_for_deployment()
# FIXME convert to unit tests
@@ -14,4 +18,4 @@ package_libraries_for_deployment()
#
# package_libraries_for_deployment()
#endmacro ()
-#setup_hifi_testcase()
+#setup_hifi_testcase()
\ No newline at end of file
diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt
index 5b83ff313b..09b2dc6a50 100644
--- a/tests/render-perf/CMakeLists.txt
+++ b/tests/render-perf/CMakeLists.txt
@@ -14,6 +14,11 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
# link in the shared libraries
link_hifi_libraries(shared networking model fbx ktx image octree gl gpu gpu-gl render model-networking networking render-utils entities entities-renderer animation audio avatars script-engine physics procedural midi ui)
+if (WIN32)
+ target_link_libraries(${TARGET_NAME} Winmm.lib)
+ add_dependency_external_projects(wasapi)
+endif()
+
package_libraries_for_deployment()
diff --git a/tests/render-texture-load/CMakeLists.txt b/tests/render-texture-load/CMakeLists.txt
index 30030914ab..432a1f00d6 100644
--- a/tests/render-texture-load/CMakeLists.txt
+++ b/tests/render-texture-load/CMakeLists.txt
@@ -26,7 +26,8 @@ target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES})
if (WIN32)
-add_paths_to_fixup_libs(${QUAZIP_DLL_PATH})
+ add_paths_to_fixup_libs(${QUAZIP_DLL_PATH})
+ add_dependency_external_projects(wasapi)
endif ()
diff --git a/tests/render-utils/CMakeLists.txt b/tests/render-utils/CMakeLists.txt
index 4944ad2cbc..4e67aef3be 100644
--- a/tests/render-utils/CMakeLists.txt
+++ b/tests/render-utils/CMakeLists.txt
@@ -11,4 +11,8 @@ setup_memory_debugger()
link_hifi_libraries(render-utils gl gpu gpu-gl shared)
target_link_libraries(${TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT})
+if (WIN32)
+ add_dependency_external_projects(wasapi)
+endif ()
+
package_libraries_for_deployment()
diff --git a/tests/shaders/CMakeLists.txt b/tests/shaders/CMakeLists.txt
index bab1e0dcdc..ba4ca88127 100644
--- a/tests/shaders/CMakeLists.txt
+++ b/tests/shaders/CMakeLists.txt
@@ -17,4 +17,8 @@ include_directories("${PROJECT_BINARY_DIR}/../../libraries/render-utils/")
include_directories("${PROJECT_BINARY_DIR}/../../libraries/entities-renderer/")
include_directories("${PROJECT_BINARY_DIR}/../../libraries/model/")
+if (WIN32)
+ add_dependency_external_projects(wasapi)
+endif ()
+
package_libraries_for_deployment()