From f32282afdd308859544924f0c8d9a05b61456af1 Mon Sep 17 00:00:00 2001
From: luiscuenca <luiscuenca@outboxcode.com>
Date: Fri, 27 Oct 2017 11:03:02 -0700
Subject: [PATCH 1/7] Clean update bug 8027

---
 .../resources/html/createGlobalEventBridge.js | 48 ++++++++++++
 interface/resources/qml/Browser.qml           | 12 ++-
 libraries/ui/CMakeLists.txt                   |  4 +-
 libraries/ui/src/ui/OffscreenQmlSurface.cpp   | 76 +++++++++++++++++++
 libraries/ui/src/ui/OffscreenQmlSurface.h     |  9 +++
 plugins/hifiSixense/CMakeLists.txt            |  3 +
 tests/controllers/CMakeLists.txt              |  3 +
 tests/render-perf/CMakeLists.txt              |  4 +
 8 files changed, 153 insertions(+), 6 deletions(-)

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 55927fda24..8a6674bc14 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 ecd07a5874..6632b669e3 100644
--- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp
+++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp
@@ -24,7 +24,11 @@
 #include <QtCore/QThread>
 #include <QtCore/QMutex>
 #include <QtCore/QWaitCondition>
+#include <QtMultimedia/qmediaservice.h>
+#include <QtMultimedia/qaudiooutputselectorcontrol.h>
+#include <QtMultimedia/qmediaplayer.h>
 
+#include <AudioClient.h>
 #include <shared/NsightHelpers.h>
 #include <shared/GlobalAppProperties.h>
 #include <shared/QtHelpers.h>
@@ -595,6 +599,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<AudioClient>();
+    connect(audioIO.data(), &AudioClient::deviceChanged, this, [&](QAudio::Mode mode, const QAudioDeviceInfo& device) {
+        if (mode == QAudio::Mode::AudioOutput) {
+            QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::DirectConnection, 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 +617,68 @@ void OffscreenQmlSurface::create() {
     _updateTimer.start();
 }
 
+void OffscreenQmlSurface::changeAudioOutputDevice(const QString& deviceName, bool isHtmlUpdate) {
+    if (_rootItem != nullptr && !isHtmlUpdate) {
+        QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::DirectConnection);
+    }
+    emit audioOutputDeviceChanged(deviceName);
+}
+
+void OffscreenQmlSurface::forceHtmlAudioOutputDeviceUpdate() {
+    auto audioIO = DependencyManager::get<AudioClient>();
+    QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName();
+    QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::DirectConnection,
+        Q_ARG(QString, deviceName), Q_ARG(bool, true));
+}
+
+void OffscreenQmlSurface::forceQmlAudioOutputDeviceUpdate() {
+    if (QThread::currentThread() != qApp->thread()) {
+        QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection);
+    }
+    else {
+        int waitForAudioQmlMs = 500;
+        QTimer::singleShot(waitForAudioQmlMs, this, SLOT(updateQmlAudio()));
+    }
+}
+
+void OffscreenQmlSurface::updateQmlAudio() {
+    auto audioIO = DependencyManager::get<AudioClient>();
+    QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName();
+    for (auto player : _rootItem->findChildren<QMediaPlayer*>()) {
+        auto mediaState = player->state();
+        QMediaService *svc = player->service();
+        if (nullptr == svc) {
+            return;
+        }
+        QAudioOutputSelectorControl *out = qobject_cast<QAudioOutputSelectorControl *>
+            (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;
+}
+
 static uvec2 clampSize(const uvec2& size, uint32_t maxDimension) {
     return glm::clamp(size, glm::uvec2(1), glm::uvec2(maxDimension));
 }
@@ -798,6 +872,7 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
         if (newItem) {
             newItem->setParentItem(_rootItem);
         }
+        QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection);
         return;
     }
 
@@ -817,6 +892,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 12ee9e59a1..0d053b373d 100644
--- a/libraries/ui/src/ui/OffscreenQmlSurface.h
+++ b/libraries/ui/src/ui/OffscreenQmlSurface.h
@@ -103,6 +103,15 @@ 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();
+    void updateQmlAudio();
+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..a503fc5710 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..623ee7d20e 100644
--- a/tests/controllers/CMakeLists.txt
+++ b/tests/controllers/CMakeLists.txt
@@ -17,5 +17,8 @@ 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)
+endif()
 
 package_libraries_for_deployment()
diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt
index 5b83ff313b..b7f13c88c5 100644
--- a/tests/render-perf/CMakeLists.txt
+++ b/tests/render-perf/CMakeLists.txt
@@ -14,6 +14,10 @@ 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)
+endif()
+
 package_libraries_for_deployment()
 
 

From 70b37f2e8a4dede381fdb64696be6c29f526862c Mon Sep 17 00:00:00 2001
From: luiscuenca <luiscuenca@outboxcode.com>
Date: Fri, 27 Oct 2017 11:32:44 -0700
Subject: [PATCH 2/7] Cleaning some old tabs

---
 interface/resources/qml/Browser.qml | 14 +++++++-------
 plugins/hifiSixense/CMakeLists.txt  |  6 +++---
 tests/controllers/CMakeLists.txt    |  2 +-
 tests/render-perf/CMakeLists.txt    |  2 +-
 4 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml
index 8a6674bc14..1dfc6875bb 100644
--- a/interface/resources/qml/Browser.qml
+++ b/interface/resources/qml/Browser.qml
@@ -233,13 +233,13 @@ ScrollingWindow {
             anchors.right: parent.right
 
             onFeaturePermissionRequested: {
-				if (feature == 2) { // QWebEnginePage::MediaAudioCapture
-					grantFeaturePermission(securityOrigin, feature, true);
-				} else {
-					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/plugins/hifiSixense/CMakeLists.txt b/plugins/hifiSixense/CMakeLists.txt
index a503fc5710..6e642fce29 100644
--- a/plugins/hifiSixense/CMakeLists.txt
+++ b/plugins/hifiSixense/CMakeLists.txt
@@ -15,7 +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()
+    if (WIN32)
+        target_link_libraries(${TARGET_NAME} Winmm.lib)
+    endif()
 endif ()
diff --git a/tests/controllers/CMakeLists.txt b/tests/controllers/CMakeLists.txt
index 623ee7d20e..062d69ca03 100644
--- a/tests/controllers/CMakeLists.txt
+++ b/tests/controllers/CMakeLists.txt
@@ -18,7 +18,7 @@ if (WIN32)
     target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES})
 endif()
 if (WIN32)
-	target_link_libraries(${TARGET_NAME} Winmm.lib)
+    target_link_libraries(${TARGET_NAME} Winmm.lib)
 endif()
 
 package_libraries_for_deployment()
diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt
index b7f13c88c5..59e043cd64 100644
--- a/tests/render-perf/CMakeLists.txt
+++ b/tests/render-perf/CMakeLists.txt
@@ -15,7 +15,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
 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)
+    target_link_libraries(${TARGET_NAME} Winmm.lib)
 endif()
 
 package_libraries_for_deployment()

From a466cf71242795deb2fb18d653c5e3e36d486034 Mon Sep 17 00:00:00 2001
From: luiscuenca <luiscuenca@outboxcode.com>
Date: Fri, 27 Oct 2017 11:51:19 -0700
Subject: [PATCH 3/7] make wasapi compile first

---
 tests/recording/CMakeLists.txt   | 4 ++++
 tests/render-perf/CMakeLists.txt | 1 +
 2 files changed, 5 insertions(+)

diff --git a/tests/recording/CMakeLists.txt b/tests/recording/CMakeLists.txt
index b5b1e6a54e..b1977c85b1 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
diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt
index 59e043cd64..09b2dc6a50 100644
--- a/tests/render-perf/CMakeLists.txt
+++ b/tests/render-perf/CMakeLists.txt
@@ -16,6 +16,7 @@ link_hifi_libraries(shared networking model fbx ktx image octree gl gpu gpu-gl r
 
 if (WIN32)
     target_link_libraries(${TARGET_NAME} Winmm.lib)
+    add_dependency_external_projects(wasapi)
 endif()
 
 package_libraries_for_deployment()

From 76223aa06f56d4bf923b7c52c75e8be5cb453424 Mon Sep 17 00:00:00 2001
From: luiscuenca <luiscuenca@outboxcode.com>
Date: Fri, 27 Oct 2017 13:07:16 -0700
Subject: [PATCH 4/7] Force wasapi to compile with domain-server (TEST)

---
 domain-server/CMakeLists.txt     | 1 +
 tests/recording/CMakeLists.txt   | 1 -
 tests/render-perf/CMakeLists.txt | 1 -
 3 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt
index c1e275e4d3..4004bee598 100644
--- a/domain-server/CMakeLists.txt
+++ b/domain-server/CMakeLists.txt
@@ -44,6 +44,7 @@ if (UNIX)
 endif ()
 
 if (WIN32)
+  add_dependency_external_projects(wasapi)
   package_libraries_for_deployment()
 endif ()
 
diff --git a/tests/recording/CMakeLists.txt b/tests/recording/CMakeLists.txt
index b1977c85b1..7ed4f5b1b0 100644
--- a/tests/recording/CMakeLists.txt
+++ b/tests/recording/CMakeLists.txt
@@ -6,7 +6,6 @@ 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()
 
diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt
index 09b2dc6a50..59e043cd64 100644
--- a/tests/render-perf/CMakeLists.txt
+++ b/tests/render-perf/CMakeLists.txt
@@ -16,7 +16,6 @@ link_hifi_libraries(shared networking model fbx ktx image octree gl gpu gpu-gl r
 
 if (WIN32)
     target_link_libraries(${TARGET_NAME} Winmm.lib)
-    add_dependency_external_projects(wasapi)
 endif()
 
 package_libraries_for_deployment()

From bda63e38a996c21f20f9ccb7a14c87cd7a1b4884 Mon Sep 17 00:00:00 2001
From: luiscuenca <luiscuenca@outboxcode.com>
Date: Fri, 27 Oct 2017 13:38:13 -0700
Subject: [PATCH 5/7] Updating all wasapi dependencies

---
 domain-server/CMakeLists.txt             | 1 -
 tests/controllers/CMakeLists.txt         | 1 +
 tests/entities/CMakeLists.txt            | 4 ++++
 tests/gpu-test/CMakeLists.txt            | 5 +++++
 tests/qt59/CMakeLists.txt                | 5 +++++
 tests/recording/CMakeLists.txt           | 3 ++-
 tests/render-perf/CMakeLists.txt         | 1 +
 tests/render-texture-load/CMakeLists.txt | 3 ++-
 tests/render-utils/CMakeLists.txt        | 4 ++++
 tests/shaders/CMakeLists.txt             | 4 ++++
 10 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt
index 4004bee598..c1e275e4d3 100644
--- a/domain-server/CMakeLists.txt
+++ b/domain-server/CMakeLists.txt
@@ -44,7 +44,6 @@ if (UNIX)
 endif ()
 
 if (WIN32)
-  add_dependency_external_projects(wasapi)
   package_libraries_for_deployment()
 endif ()
 
diff --git a/tests/controllers/CMakeLists.txt b/tests/controllers/CMakeLists.txt
index 062d69ca03..c3d25cfe2e 100644
--- a/tests/controllers/CMakeLists.txt
+++ b/tests/controllers/CMakeLists.txt
@@ -19,6 +19,7 @@ if (WIN32)
 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 7ed4f5b1b0..dbb942a27a 100644
--- a/tests/recording/CMakeLists.txt
+++ b/tests/recording/CMakeLists.txt
@@ -6,6 +6,7 @@ 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()
 
@@ -17,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 59e043cd64..09b2dc6a50 100644
--- a/tests/render-perf/CMakeLists.txt
+++ b/tests/render-perf/CMakeLists.txt
@@ -16,6 +16,7 @@ link_hifi_libraries(shared networking model fbx ktx image octree gl gpu gpu-gl r
 
 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()

From 8d5c95b1d6212957677d72a2cc23950a72da810a Mon Sep 17 00:00:00 2001
From: luiscuenca <luiscuenca@outboxcode.com>
Date: Mon, 30 Oct 2017 16:47:59 -0700
Subject: [PATCH 6/7] Various corrections

---
 libraries/ui/src/ui/OffscreenQmlSurface.cpp | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp
index 6632b669e3..b7f717db04 100644
--- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp
+++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp
@@ -24,9 +24,9 @@
 #include <QtCore/QThread>
 #include <QtCore/QMutex>
 #include <QtCore/QWaitCondition>
-#include <QtMultimedia/qmediaservice.h>
-#include <QtMultimedia/qaudiooutputselectorcontrol.h>
-#include <QtMultimedia/qmediaplayer.h>
+#include <QtMultimedia/QMediaService>
+#include <QtMultimedia/QAudioOutputSelectorControl>
+#include <QtMultimedia/QMediaPlayer>
 
 #include <AudioClient.h>
 #include <shared/NsightHelpers.h>
@@ -634,10 +634,11 @@ void OffscreenQmlSurface::forceHtmlAudioOutputDeviceUpdate() {
 void OffscreenQmlSurface::forceQmlAudioOutputDeviceUpdate() {
     if (QThread::currentThread() != qApp->thread()) {
         QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection);
-    }
-    else {
+    } else {
         int waitForAudioQmlMs = 500;
-        QTimer::singleShot(waitForAudioQmlMs, this, SLOT(updateQmlAudio()));
+        QTimer::singleShot(waitForAudioQmlMs, this, [this] {
+            updateQmlAudio();
+        });
     }
 }
 

From c834cf1cf3f442be3ad34da09066e65e5a9e07cc Mon Sep 17 00:00:00 2001
From: luiscuenca <luiscuenca@outboxcode.com>
Date: Wed, 1 Nov 2017 21:55:31 -0700
Subject: [PATCH 7/7] Comments and corrections

---
 libraries/ui/src/ui/OffscreenQmlSurface.cpp | 114 ++++++++++++--------
 libraries/ui/src/ui/OffscreenQmlSurface.h   |   1 -
 2 files changed, 70 insertions(+), 45 deletions(-)

diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp
index b7f717db04..8232c6a064 100644
--- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp
+++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp
@@ -117,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<AudioClient>();
+        QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName();
+        for (auto player : _container->findChildren<QMediaPlayer*>()) {
+            auto mediaState = player->state();
+            QMediaService *svc = player->service();
+            if (nullptr == svc) {
+                return;
+            }
+            QAudioOutputSelectorControl *out = qobject_cast<QAudioOutputSelectorControl *>
+                (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) {
@@ -604,7 +667,7 @@ void OffscreenQmlSurface::create() {
     auto audioIO = DependencyManager::get<AudioClient>();
     connect(audioIO.data(), &AudioClient::deviceChanged, this, [&](QAudio::Mode mode, const QAudioDeviceInfo& device) {
         if (mode == QAudio::Mode::AudioOutput) {
-            QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::DirectConnection, Q_ARG(QString, device.deviceName()));
+            QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::QueuedConnection, Q_ARG(QString, device.deviceName()));
         }
     });
 
@@ -619,7 +682,7 @@ void OffscreenQmlSurface::create() {
 
 void OffscreenQmlSurface::changeAudioOutputDevice(const QString& deviceName, bool isHtmlUpdate) {
     if (_rootItem != nullptr && !isHtmlUpdate) {
-        QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::DirectConnection);
+        QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection);
     }
     emit audioOutputDeviceChanged(deviceName);
 }
@@ -627,7 +690,7 @@ void OffscreenQmlSurface::changeAudioOutputDevice(const QString& deviceName, boo
 void OffscreenQmlSurface::forceHtmlAudioOutputDeviceUpdate() {
     auto audioIO = DependencyManager::get<AudioClient>();
     QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName();
-    QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::DirectConnection,
+    QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::QueuedConnection,
         Q_ARG(QString, deviceName), Q_ARG(bool, true));
 }
 
@@ -635,51 +698,14 @@ void OffscreenQmlSurface::forceQmlAudioOutputDeviceUpdate() {
     if (QThread::currentThread() != qApp->thread()) {
         QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection);
     } else {
+        auto audioIO = DependencyManager::get<AudioClient>();
+        QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName();
         int waitForAudioQmlMs = 500;
-        QTimer::singleShot(waitForAudioQmlMs, this, [this] {
-            updateQmlAudio();
-        });
+        // The audio device need to be change using oth
+        new AudioHandler(_rootItem, deviceName, waitForAudioQmlMs);        
     }
 }
 
-void OffscreenQmlSurface::updateQmlAudio() {
-    auto audioIO = DependencyManager::get<AudioClient>();
-    QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName();
-    for (auto player : _rootItem->findChildren<QMediaPlayer*>()) {
-        auto mediaState = player->state();
-        QMediaService *svc = player->service();
-        if (nullptr == svc) {
-            return;
-        }
-        QAudioOutputSelectorControl *out = qobject_cast<QAudioOutputSelectorControl *>
-            (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;
-}
-
 static uvec2 clampSize(const uvec2& size, uint32_t maxDimension) {
     return glm::clamp(size, glm::uvec2(1), glm::uvec2(maxDimension));
 }
diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h
index 0d053b373d..2d64ae12b5 100644
--- a/libraries/ui/src/ui/OffscreenQmlSurface.h
+++ b/libraries/ui/src/ui/OffscreenQmlSurface.h
@@ -108,7 +108,6 @@ public slots:
     void changeAudioOutputDevice(const QString& deviceName, bool isHtmlUpdate = false);
     void forceHtmlAudioOutputDeviceUpdate();
     void forceQmlAudioOutputDeviceUpdate();
-    void updateQmlAudio();
 signals:
     void audioOutputDeviceChanged(const QString& deviceName);