Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwasmmediadevices.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include "private/qcameradevice_p.h"
6#include "private/qplatformmediaintegration_p.h"
10#include <AL/al.h>
11#include <AL/alc.h>
12
13#include <QMap>
14#include <QDebug>
15
17
18Q_LOGGING_CATEGORY(qWasmMediaDevices, "qt.multimedia.wasm.mediadevices")
19
20Q_GLOBAL_STATIC(QWasmMediaDevices, s_wasmMediaDevicesInstance);
21
22QWasmCameraDevices::QWasmCameraDevices(QPlatformMediaIntegration *integration)
23 : QPlatformVideoDevices(integration)
24{
25}
26
31
32void QWasmCameraDevices::connectNotify(const QMetaMethod &signal)
33{
34 Q_ASSERT(QThread::isMainThread());
36}
37
39
41 const QAudioFormat &fmt,
42 QObject *parent)
43{
44 return new QWasmAudioSource(deviceInfo, fmt, parent);
45}
46
47QPlatformAudioSink *QWasmAudioDevices::createAudioSink(const QAudioDevice &deviceInfo,
48 const QAudioFormat &fmt,
49 QObject *parent)
50{
51 return new QWasmAudioSink(deviceInfo, fmt, parent);
52}
53
58
63
64void QWasmAudioDevices::connectNotify(const QMetaMethod &signal)
65{
66 Q_ASSERT(QThread::isMainThread());
68}
69
74
76{
77 return s_wasmMediaDevicesInstance();
78}
79
81{
82 if (m_initDone)
83 return;
84
85 m_initDone = true;
86 getOpenALAudioDevices();
87 getMediaDevices(); // asynchronous
88}
89
91{
92 return m_cameraDevices.values();
93}
94
96{
97 return m_audioInputs.values();
98}
99
101{
102 return m_audioOutputs.values();
103}
104
105void QWasmMediaDevices::parseDevices(emscripten::val devices)
106{
107 if (devices.isNull() || devices.isUndefined()) {
108 qWarning() << "Something went wrong enumerating devices";
109 return;
110 }
111
112 QList<std::string> cameraDevicesToRemove = m_cameraDevices.keys();
113 QList<std::string> audioOutputsToRemove;
114 QList<std::string> audioInputsToRemove;
115
116 if (m_firstInit) {
117 m_firstInit = false;
118 qWarning() << "m_audioInputs count" << m_audioInputs.count();
119
120 } else {
121 audioOutputsToRemove = m_audioOutputs.keys();
122 audioInputsToRemove = m_audioInputs.keys();
123 m_audioInputsAdded = false;
124 m_audioOutputsAdded = false;
125 }
126 m_videoInputsAdded = false;
127
128 bool m_videoInputsRemoved = false;
129 bool m_audioInputsRemoved = false;
130 bool m_audioOutputsRemoved = false;
131
132 for (int i = 0; i < devices["length"].as<int>(); i++) {
133
134 emscripten::val mediaDevice = devices[i];
135
136 const std::string deviceKind = mediaDevice["kind"].as<std::string>();
137 std::string label = mediaDevice["label"].as<std::string>();
138 std::string deviceId = mediaDevice["deviceId"].as<std::string>();
139
140 qCDebug(qWasmMediaDevices) << QString::fromStdString(deviceKind)
141 << QString::fromStdString(deviceId)
142 << QString::fromStdString(label);
143
144 if (deviceId.empty()) { // no permissions we'll use System;
145 label = "System " + deviceKind;
146 deviceId = label;
147 }
148 if (deviceKind.empty())
149 continue;
150 bool isDefault = false;
151
152 if (deviceKind == std::string("videoinput")) {
153 if (!m_cameraDevices.contains(deviceId)) {
154 QCameraDevicePrivate *camera = new QCameraDevicePrivate; // QSharedData
155 camera->id = QString::fromStdString(deviceId).toUtf8();
156 camera->description = QString::fromUtf8(label.c_str());
157 // no camera defaults, first in wins!
158 camera->isDefault = !m_videoInputsAdded;
159 m_cameraDevices.insert(deviceId, camera->create());
160 m_videoInputsAdded = true;
161 }
162 cameraDevicesToRemove.removeOne(deviceId);
163 } else if (deviceKind == std::string("audioinput")) {
164 if (!m_audioInputs.contains(deviceId)) {
165 isDefault = !m_audioInputsAdded;
166 m_audioInputs.insert(
167 deviceId,
168 QAudioDevicePrivate::createQAudioDevice(std::make_unique<QWasmAudioDevice>(
169 deviceId.c_str(), label.c_str(), isDefault, QAudioDevice::Input)));
170
171 m_audioInputsAdded = true;
172 }
173 audioInputsToRemove.removeOne(deviceId);
174 } else if (deviceKind == std::string("audiooutput")) {
175 if (!m_audioOutputs.contains(deviceId)) {
176 isDefault = !m_audioOutputsAdded;
177 m_audioOutputs.insert(
178 deviceId,
179 QAudioDevicePrivate::createQAudioDevice(std::make_unique<QWasmAudioDevice>(
180 deviceId.c_str(), label.c_str(), isDefault, QAudioDevice::Output)));
181 ;
182
183 m_audioOutputsAdded = true;
184 }
185 audioOutputsToRemove.removeOne(deviceId);
186 }
187 // if permissions are given label will hold the actual
188 // camera name, such as "Live! Cam Sync 1080p (041e:409d)"
189 }
190 if (!m_firstInit)
191 getOpenALAudioDevices();
192
193 // any left here were removed
194 int j = 0;
195 for (; j < cameraDevicesToRemove.count(); j++) {
196 m_cameraDevices.remove(cameraDevicesToRemove.at(j));
197 }
198 m_videoInputsRemoved = !cameraDevicesToRemove.isEmpty();
199
200 for (j = 0; j < audioInputsToRemove.count(); j++) {
201 m_audioInputs.remove(audioInputsToRemove.at(j));
202 }
203 m_audioInputsRemoved = !audioInputsToRemove.isEmpty();
204
205 for (j = 0; j < audioOutputsToRemove.count(); j++) {
206 m_audioOutputs.remove(audioOutputsToRemove.at(j));
207 }
208 m_audioOutputsRemoved = !audioOutputsToRemove.isEmpty();
209
210 if (m_videoInputsAdded || m_videoInputsRemoved) {
211 auto videoDevices = static_cast<QWasmCameraDevices*>(QPlatformMediaIntegration::instance()->videoDevices());
212 videoDevices->onVideoInputsChanged();
213 }
214 if (m_audioInputsAdded || m_audioInputsRemoved) {
215 auto audioDevices = static_cast<QWasmAudioDevices*>(QPlatformMediaIntegration::instance()->audioDevices());
216 audioDevices->onAudioInputsChanged();
217 }
218 if (m_audioOutputsAdded || m_audioOutputsRemoved) {
219 auto audioDevices = static_cast<QWasmAudioDevices*>(QPlatformMediaIntegration::instance()->audioDevices());
220 audioDevices->onAudioOutputsChanged();
221 }
222
223 m_firstInit = false;
224
225}
226
227void QWasmMediaDevices::getMediaDevices()
228{
229 emscripten::val navigator = emscripten::val::global("navigator");
230 m_jsMediaDevicesInterface = navigator["mediaDevices"];
231
232 if (m_jsMediaDevicesInterface.isNull() || m_jsMediaDevicesInterface.isUndefined()) {
233 qWarning() << "No media devices found";
234 return;
235 }
236
237 if (qstdweb::haveAsyncify()) {
238
239#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY
240 auto asyncEnumerate = [](void *arg){
241 QWasmMediaDevices *mediaDevices = static_cast<QWasmMediaDevices *>(arg);
242 mediaDevices->devicesList = mediaDevices->m_jsMediaDevicesInterface.call<emscripten::val>("enumerateDevices").await();
243 if (mediaDevices->devicesList.isNull() || mediaDevices->devicesList.isUndefined()) {
244 qWarning() << "devices list error";
245 return;
246 }
247 mediaDevices->parseDevices(mediaDevices->devicesList);
248 };
249
250 asyncEnumerate(this);
251
252 m_deviceChangedCallback = std::make_unique<qstdweb::EventCallback>(
253 m_jsMediaDevicesInterface, "devicechange",
254 [this, asyncEnumerate](emscripten::val) {
255 asyncEnumerate(this);
256 });
257#endif
258
259 } else {
260
261 qstdweb::PromiseCallbacks enumerateDevicesCallback{
262 .thenFunc =
263 [&](emscripten::val devices) {
264 parseDevices(devices);
265 },
266 .catchFunc =
267 [this](emscripten::val error) {
268 qWarning() << "mediadevices enumerateDevices fail"
269 << QString::fromStdString(error["name"].as<std::string>())
270 << QString::fromStdString(error["message"].as<std::string>());
271 m_initDone = false;
272 }
273 };
274
275 qstdweb::Promise::make(m_jsMediaDevicesInterface,
276 QStringLiteral("enumerateDevices"),
277 std::move(enumerateDevicesCallback));
278
279 // setup devicechange monitor
280 m_deviceChangedCallback = std::make_unique<qstdweb::EventCallback>(
281 m_jsMediaDevicesInterface, "devicechange",
282 [this, enumerateDevicesCallback](emscripten::val) {
283 qstdweb::Promise::make(m_jsMediaDevicesInterface,
284 QStringLiteral("enumerateDevices"),
285 std::move(enumerateDevicesCallback));
286 });
287 }
288
289}
290
291void QWasmMediaDevices::getOpenALAudioDevices()
292{
293 // VM3959:4 The AudioContext was not allowed to start.
294 // It must be resumed (or created) after a user gesture on the page. https://goo.gl/7K7WLu
295 auto capture = alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
296 // present even if there is no capture device
297 if (capture && !m_audioOutputs.contains(capture)) {
298 m_audioInputs.insert(
299 capture,
300 QAudioDevicePrivate::createQAudioDevice(std::make_unique<QWasmAudioDevice>(
301 capture, "WebAssembly audio capture device", true, QAudioDevice::Input)));
302 m_audioInputsAdded = true;
303 auto audioDevices = static_cast<QWasmAudioDevices*>(QPlatformMediaIntegration::instance()->audioDevices());
304 audioDevices->onAudioInputsChanged();
305 }
306
307 auto playback = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER);
308 // present even if there is no playback device
309 if (playback && !m_audioOutputs.contains(capture)) {
310 m_audioOutputs.insert(
311 playback,
312 QAudioDevicePrivate::createQAudioDevice(std::make_unique<QWasmAudioDevice>(
313 playback, "WebAssembly audio playback device", true,
314 QAudioDevice::Output)));
315 auto audioDevices = static_cast<QWasmAudioDevices*>(QPlatformMediaIntegration::instance()->audioDevices());
316 audioDevices->onAudioOutputsChanged();
317 }
318 m_firstInit = true;
319}
320
321QT_END_NAMESPACE
The QAudioDevice class provides an information about audio devices and their functionality.
The QCameraDevice class provides general information about camera devices.
QPlatformAudioSink * createAudioSink(const QAudioDevice &, const QAudioFormat &, QObject *parent) override
QList< QAudioDevice > findAudioOutputs() const override
QList< QAudioDevice > findAudioInputs() const override
QPlatformAudioSource * createAudioSource(const QAudioDevice &, const QAudioFormat &, QObject *parent) override
void connectNotify(const QMetaMethod &signal) override
void connectNotify(const QMetaMethod &signal) override
QList< QCameraDevice > findVideoInputs() const override
QList< QCameraDevice > videoInputs() const
static QWasmMediaDevices * instance()
QList< QAudioDevice > audioOutputs() const
QList< QAudioDevice > audioInputs() const
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")