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