5#include "private/qcameradevice_p.h"
6#include "private/qplatformmediaintegration_p.h"
10#include <QtMultimedia/private/qmultimedia_ranges_p.h>
14#include <emscripten.h>
22namespace ranges = QtMultimediaPrivate::ranges;
37 return deviceId == s_defaultDeviceId || deviceId == s_communicationsDeviceId;
42 const emscripten::val value = mediaDevice[property];
43 if (value.isUndefined() || value.isNull())
45 return value.as<
std::string>();
52 return deviceId.empty() ?
"System " + deviceKind : deviceId;
55template <
typename DeviceList>
58 return ranges::any_of(devices, [&deviceId](
const auto &device) {
59 return device.id() == deviceId;
69 return !emscripten::val::global(
"InstallTrigger").isUndefined();
84 overlay.
style.
cssText =
'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9999;display:flex;align-items:center;justify-content:center;';
87 dialog.
style.
cssText =
'background:white;padding:24px;border-radius:8px;text-align:center;font-family:sans-serif;min-width:240px;';
115 : QPlatformVideoDevices(integration)
126 Q_ASSERT(QThread::isMainThread());
133 const QAudioFormat &fmt,
140 const QAudioFormat &fmt,
158 Q_ASSERT(QThread::isMainThread());
189 insertFallbackAudioOutput();
192 setupAudioOutputSelector();
199 return m_cameraDevices;
204 return m_audioInputs;
209 return m_audioOutputs;
217 std::string_view deviceKind)
const
219 const QString kindName = QString::fromUtf8(deviceKind.data(), qsizetype(deviceKind.size()));
220 std::string defaultGroupId;
221 QList<std::pair<QByteArray, std::string>> candidates;
223 const int deviceCount = devices[
"length"].as<
int>();
224 for (
int i = 0; i < deviceCount; ++i) {
225 const emscripten::val mediaDevice = devices[i];
226 const std::string kind = stringProperty(mediaDevice,
"kind");
227 if (kind != deviceKind)
230 const std::string deviceId = stringProperty(mediaDevice,
"deviceId");
231 if (deviceId == s_defaultDeviceId) {
232 defaultGroupId = stringProperty(mediaDevice,
"groupId");
238 candidates.append({ QByteArray::fromStdString(effectiveDeviceId(deviceId, kind)),
239 stringProperty(mediaDevice,
"groupId") });
242 if (candidates.isEmpty()) {
243 qCDebug(qWasmMediaDevices) <<
"no" << kindName <<
"devices enumerated, no default";
247 if (!defaultGroupId.empty()) {
248 const auto it = ranges::find_if(candidates, [&defaultGroupId](
const auto &candidate) {
249 return candidate.second == defaultGroupId;
251 if (it != candidates.cend()) {
252 qCDebug(qWasmMediaDevices)
253 <<
"default" << kindName <<
"resolved from the browser's default entry:"
254 << it->first <<
"via groupId" << QString::fromStdString(defaultGroupId);
257 qCDebug(qWasmMediaDevices)
258 <<
"the browser's default" << kindName <<
"entry has groupId"
259 << QString::fromStdString(defaultGroupId)
260 <<
"which matches no enumerated device, falling back to the first";
262 qCDebug(qWasmMediaDevices) <<
"default" << kindName
263 <<
"falls back to the first enumerated device:"
264 << candidates.constFirst().first;
265 return candidates.constFirst().first;
269 const QByteArray &defaultDeviceId)
271 for (QAudioDevice &audioDevice : audioDevices) {
272 QAudioDevicePrivate *devicePrivate = QAudioDevicePrivate::handle(audioDevice);
275 const bool isDefault = audioDevice.id() == defaultDeviceId;
276 if (devicePrivate->isDefault == isDefault)
278 qCDebug(qWasmMediaDevices) <<
"default" << audioDevice.mode()
279 << (isDefault ?
"set to" :
"cleared from")
280 << audioDevice.id() << audioDevice.description();
281 devicePrivate->isDefault = isDefault;
286 const QByteArray &defaultDeviceId)
288 for (QCameraDevice &cameraDevice : cameraDevices) {
292 const bool isDefault = cameraDevice.id() == defaultDeviceId;
293 if (devicePrivate->isDefault == isDefault)
295 qCDebug(qWasmMediaDevices) <<
"default camera"
296 << (isDefault ?
"set to" :
"cleared from")
297 << cameraDevice.id() << cameraDevice.description();
298 devicePrivate->isDefault = isDefault;
304 if (devices.isNull() || devices.isUndefined()) {
305 qWarning() <<
"Something went wrong enumerating devices";
309 QList<QByteArray> cameraDevicesToRemove;
310 cameraDevicesToRemove.reserve(m_cameraDevices.count());
311 for (
const QCameraDevice &cameraDevice : m_cameraDevices)
312 cameraDevicesToRemove.append(cameraDevice.id());
314 QList<QByteArray> audioOutputsToRemove;
315 audioOutputsToRemove.reserve(m_audioOutputs.count());
316 for (
const QAudioDevice &audioDevice : m_audioOutputs)
317 audioOutputsToRemove.append(audioDevice.id());
319 QList<QByteArray> audioInputsToRemove;
320 audioInputsToRemove.reserve(m_audioInputs.count());
321 for (
const QAudioDevice &audioDevice : m_audioInputs)
322 audioInputsToRemove.append(audioDevice.id());
323 m_audioInputsAdded =
false;
324 m_audioOutputsAdded =
false;
325 m_videoInputsAdded =
false;
327 const QByteArray defaultCameraId = findDefaultDeviceId(devices,
"videoinput");
328 const QByteArray defaultAudioInputId = findDefaultDeviceId(devices,
"audioinput");
329 const QByteArray defaultAudioOutputId = findDefaultDeviceId(devices,
"audiooutput");
331 bool m_videoInputsRemoved =
false;
332 bool m_audioInputsRemoved =
false;
333 bool m_audioOutputsRemoved =
false;
335 for (
int i = 0; i < devices[
"length"].as<
int>(); i++) {
337 emscripten::val mediaDevice = devices[i];
339 const std::string deviceKind = mediaDevice[
"kind"].as<
std::string>();
340 std::string label = mediaDevice[
"label"].as<
std::string>();
341 std::string deviceId = mediaDevice[
"deviceId"].as<
std::string>();
343 if (deviceKind.empty())
346 qCDebug(qWasmMediaDevices) << QString::fromStdString(deviceKind)
347 << QString::fromStdString(deviceId)
348 << QString::fromStdString(label) <<
"groupId"
349 << QString::fromStdString(stringProperty(mediaDevice,
"groupId"));
355 if (deviceId.empty()) {
356 label =
"System " + deviceKind;
360 const QByteArray qtDeviceId = QByteArray::fromStdString(deviceId);
362 if (deviceKind == std::string_view(
"videoinput")) {
363 if (!containsDeviceId(m_cameraDevices, qtDeviceId)) {
365 camera->id = qtDeviceId;
366 camera->description = QString::fromUtf8(label.c_str());
367 camera->isDefault =
false;
368 m_cameraDevices.append(camera->create());
369 m_videoInputsAdded =
true;
371 cameraDevicesToRemove.removeOne(qtDeviceId);
372 }
else if (deviceKind == std::string_view(
"audioinput")) {
373 if (!containsDeviceId(m_audioInputs, qtDeviceId)) {
374 m_audioInputs.append(
375 QAudioDevicePrivate::createQAudioDevice(std::make_unique<QWasmAudioDevice>(
376 deviceId.c_str(), label.c_str(),
false, QAudioDevice::Input)));
378 m_audioInputsAdded =
true;
380 audioInputsToRemove.removeOne(qtDeviceId);
381 }
else if (deviceKind == std::string_view(
"audiooutput")) {
382 if (!containsDeviceId(m_audioOutputs, qtDeviceId)) {
383 m_audioOutputs.append(
384 QAudioDevicePrivate::createQAudioDevice(std::make_unique<QWasmAudioDevice>(
385 deviceId.c_str(), label.c_str(),
false, QAudioDevice::Output)));
387 m_audioOutputsAdded =
true;
389 audioOutputsToRemove.removeOne(qtDeviceId);
395 for (
const QByteArray &cameraId : cameraDevicesToRemove) {
396 m_cameraDevices.removeIf([&](
const QCameraDevice &cameraDevice) {
397 return cameraDevice.id() == cameraId;
400 m_videoInputsRemoved = !cameraDevicesToRemove.isEmpty();
402 for (
const QByteArray &audioDeviceId : audioInputsToRemove) {
403 m_audioInputs.removeIf([&audioDeviceId](
const QAudioDevice &audioDevice) {
404 return audioDevice.id() == audioDeviceId;
407 m_audioInputsRemoved = !audioInputsToRemove.isEmpty();
409 for (
const QByteArray &audioDeviceId : audioOutputsToRemove) {
410 m_audioOutputs.removeIf([&audioDeviceId](
const QAudioDevice &audioDevice) {
411 return audioDevice.id() == audioDeviceId;
414 m_audioOutputsRemoved = !audioOutputsToRemove.isEmpty();
416 if (!m_audioOutputsAdded) {
421 insertFallbackAudioOutput();
426 applyDefaultDevice(m_cameraDevices, defaultCameraId);
427 applyDefaultDevice(m_audioInputs, defaultAudioInputId);
428 applyDefaultDevice(m_audioOutputs, defaultAudioOutputId);
430 if (m_videoInputsAdded || m_videoInputsRemoved) {
431 auto videoDevices =
static_cast<QWasmCameraDevices*>(QPlatformMediaIntegration::instance()->videoDevices());
432 QMetaObject::invokeMethod(videoDevices, &QWasmCameraDevices::onVideoInputsChanged,
433 Qt::QueuedConnection);
435 if (m_audioInputsAdded || m_audioInputsRemoved) {
436 auto audioDevices =
static_cast<QWasmAudioDevices*>(QPlatformMediaIntegration::instance()->audioDevices());
437 QMetaObject::invokeMethod(audioDevices, &QWasmAudioDevices::onAudioInputsChanged,
438 Qt::QueuedConnection);
440 if (m_audioOutputsAdded || m_audioOutputsRemoved) {
441 auto audioDevices =
static_cast<QWasmAudioDevices*>(QPlatformMediaIntegration::instance()->audioDevices());
442 QMetaObject::invokeMethod(audioDevices, &QWasmAudioDevices::onAudioOutputsChanged,
443 Qt::QueuedConnection);
450 const bool hasFallback =
std::any_of(m_audioOutputs.cbegin(), m_audioOutputs.cend(),
451 [](
const QAudioDevice &audioDevice) {
452 return audioDevice.id().isEmpty();
455 m_audioOutputs.append(
456 QAudioDevicePrivate::createQAudioDevice(std::make_unique<QWasmAudioDevice>(
457 "",
"System output",
true, QAudioDevice::Output)));
459 m_audioOutputsAdded =
true;
464 emscripten::val navigator = emscripten::val::global(
"navigator");
465 m_jsMediaDevicesInterface = navigator[
"mediaDevices"];
467 if (m_jsMediaDevicesInterface.isNull() || m_jsMediaDevicesInterface.isUndefined()) {
468 qWarning() <<
"No media devices found";
472 if (qstdweb::haveAsyncify()) {
474#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY
475 auto asyncEnumerate = [](
void *arg){
476 QWasmMediaDevices *mediaDevices =
static_cast<QWasmMediaDevices *>(arg);
477 mediaDevices->devicesList = mediaDevices->m_jsMediaDevicesInterface.call<emscripten::val>(
"enumerateDevices").await();
478 if (mediaDevices->devicesList.isNull() || mediaDevices->devicesList.isUndefined()) {
479 qWarning() <<
"devices list error";
482 mediaDevices->parseDevices(mediaDevices->devicesList);
485 asyncEnumerate(
this);
487 m_deviceChangedCallback = std::make_unique<qstdweb::EventCallback>(
488 m_jsMediaDevicesInterface,
"devicechange",
489 [
this, asyncEnumerate](emscripten::val) {
490 asyncEnumerate(
this);
496 qstdweb::PromiseCallbacks enumerateDevicesCallback{
498 [&](emscripten::val devices) {
499 parseDevices(devices);
502 [
this](emscripten::val error) {
503 qWarning() <<
"mediadevices enumerateDevices fail"
504 << QString::fromStdString(error[
"name"].as<std::string>())
505 << QString::fromStdString(error[
"message"].as<std::string>());
511 qstdweb::Promise::make(m_jsMediaDevicesInterface,
512 QStringLiteral(
"enumerateDevices"),
513 std::move(enumerateDevicesCallback));
516 m_deviceChangedCallback = std::make_unique<qstdweb::EventCallback>(
517 m_jsMediaDevicesInterface,
"devicechange",
518 [
this, enumerateDevicesCallback](emscripten::val) {
519 qstdweb::Promise::make(m_jsMediaDevicesInterface,
520 QStringLiteral(
"enumerateDevices"),
521 std::move(enumerateDevicesCallback));
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
std::string effectiveDeviceId(const std::string &deviceId, const std::string &deviceKind)
bool containsDeviceId(const DeviceList &devices, const QByteArray &deviceId)
constexpr std::string_view s_communicationsDeviceId
constexpr std::string_view s_defaultDeviceId
std::string stringProperty(const emscripten::val &mediaDevice, const char *property)
bool isAliasDeviceId(std::string_view deviceId)
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")