//// AudioDevices.cpp// interface/src/scripting//// Created by Zach Pomerantz on 28/5/2017.// Copyright 2017 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include <map> #include <shared/QtHelpers.h> #include "AudioDevices.h" #include "Application.h"#include "AudioClient.h"#include "Audio.h" #include "UserActivityLogger.h" using namespace scripting; static Setting::Handle<QString> desktopInputDeviceSetting { QStringList { Audio::AUDIO, Audio::DESKTOP, "INPUT" }};static Setting::Handle<QString> desktopOutputDeviceSetting { QStringList { Audio::AUDIO, Audio::DESKTOP, "OUTPUT" }};static Setting::Handle<QString> hmdInputDeviceSetting { QStringList { Audio::AUDIO, Audio::HMD, "INPUT" }};static Setting::Handle<QString> hmdOutputDeviceSetting { QStringList { Audio::AUDIO, Audio::HMD, "OUTPUT" }}; Setting::Handle<QString>& getSetting(bool contextIsHMD, QAudio::Mode mode) { if (mode == QAudio::AudioInput) { return contextIsHMD ? hmdInputDeviceSetting : desktopInputDeviceSetting; } else { // if (mode == QAudio::AudioOutput) return contextIsHMD ? hmdOutputDeviceSetting : desktopOutputDeviceSetting; }} QHash<int, QByteArray> AudioDeviceList::_roles { { Qt::DisplayRole, "display" }, { Qt::CheckStateRole, "selected" }, { Qt::UserRole, "info" }};Qt::ItemFlags AudioDeviceList::_flags { Qt::ItemIsSelectable | Qt::ItemIsEnabled }; QVariant AudioDeviceList::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() >= _devices.size()) { return QVariant(); } if (role == Qt::DisplayRole) { return _devices.at(index.row()).display; } else if (role == Qt::CheckStateRole) { return _devices.at(index.row()).selected; } else if (role == Qt::UserRole) { return QVariant::fromValue<QAudioDeviceInfo>(_devices.at(index.row()).info); } else { return QVariant(); }} void AudioDeviceList::resetDevice(bool contextIsHMD, const QString& device) { auto client = DependencyManager::get<AudioClient>().data(); auto deviceName = getSetting(contextIsHMD, _mode).get(); bool switchResult = false; QMetaObject::invokeMethod(client, "switchAudioDevice", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, switchResult), Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName)); // try to set to the default device for this mode if (!switchResult) { if (contextIsHMD) { QString deviceName; if (_mode == QAudio::AudioInput) { deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioInDevice(); } else { // if (_mode == QAudio::AudioOutput) deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice(); } if (!deviceName.isNull()) { QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName)); } } else { // use the system default QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudio::Mode, _mode)); } }} void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device) { auto oldDevice = _selectedDevice; _selectedDevice = device; for (auto i = 0; i < _devices.size(); ++i) { AudioDevice& device = _devices[i]; if (device.selected && device.info != _selectedDevice) { device.selected = false; } else if (device.info == _selectedDevice) { device.selected = true; } } emit deviceChanged(_selectedDevice); emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0));} void AudioDeviceList::onDevicesChanged(const QList<QAudioDeviceInfo>& devices) { beginResetModel(); _devices.clear(); foreach(const QAudioDeviceInfo& deviceInfo, devices) { AudioDevice device; device.info = deviceInfo; device.display = device.info.deviceName() .replace("High Definition", "HD") .remove("Device") .replace(" )", ")"); device.selected = (device.info == _selectedDevice); _devices.push_back(device); } endResetModel();} AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) { auto client = DependencyManager::get<AudioClient>(); connect(client.data(), &AudioClient::deviceChanged, this, &AudioDevices::onDeviceChanged, Qt::QueuedConnection); connect(client.data(), &AudioClient::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection); // connections are made after client is initialized, so we must also fetch the devices _inputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioInput)); _outputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioOutput)); _inputs.onDevicesChanged(client->getAudioDevices(QAudio::AudioInput)); _outputs.onDevicesChanged(client->getAudioDevices(QAudio::AudioOutput));} void AudioDevices::onContextChanged(const QString& context) { auto input = getSetting(_contextIsHMD, QAudio::AudioInput).get(); auto output = getSetting(_contextIsHMD, QAudio::AudioOutput).get(); _inputs.resetDevice(_contextIsHMD, input); _outputs.resetDevice(_contextIsHMD, output);} void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice) { QString deviceName = device.isNull() ? QString() : device.deviceName(); auto& setting = getSetting(_contextIsHMD, mode); // check for a previous device auto wasDefault = setting.get().isNull(); // store the selected device setting.set(deviceName); // log the selected device if (!device.isNull()) { QJsonObject data; const QString MODE = "audio_mode"; const QString INPUT = "INPUT"; const QString OUTPUT = "OUTPUT"; data[MODE] = mode == QAudio::AudioInput ? INPUT : OUTPUT; const QString CONTEXT = "display_mode"; data[CONTEXT] = _contextIsHMD ? Audio::HMD : Audio::DESKTOP; const QString DISPLAY = "display_device"; data[DISPLAY] = qApp->getActiveDisplayPlugin()->getName(); const QString DEVICE = "device"; const QString PREVIOUS_DEVICE = "previous_device"; const QString WAS_DEFAULT = "was_default"; data[DEVICE] = deviceName; data[PREVIOUS_DEVICE] = previousDevice.deviceName(); data[WAS_DEFAULT] = wasDefault; UserActivityLogger::getInstance().logAction("selected_audio_device", data); }} void AudioDevices::onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device) { if (mode == QAudio::AudioInput) { _inputs.onDeviceChanged(device); } else { // if (mode == QAudio::AudioOutput) _outputs.onDeviceChanged(device); }} void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceInfo>& devices) { static std::once_flag once; if (mode == QAudio::AudioInput) { _inputs.onDevicesChanged(devices); } else { // if (mode == QAudio::AudioOutput) _outputs.onDevicesChanged(devices); } std::call_once(once, [&] { onContextChanged(QString()); });} void AudioDevices::chooseInputDevice(const QAudioDeviceInfo& device) { auto client = DependencyManager::get<AudioClient>(); bool success = false; QMetaObject::invokeMethod(client.data(), "switchAudioDevice", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success), Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(const QAudioDeviceInfo&, device)); if (success) { onDeviceSelected(QAudio::AudioInput, device, _inputs._selectedDevice); }} void AudioDevices::chooseOutputDevice(const QAudioDeviceInfo& device) { auto client = DependencyManager::get<AudioClient>(); bool success = false; QMetaObject::invokeMethod(client.data(), "switchAudioDevice", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success), Q_ARG(QAudio::Mode, QAudio::AudioOutput), Q_ARG(const QAudioDeviceInfo&, device)); if (success) { onDeviceSelected(QAudio::AudioOutput, device, _outputs._selectedDevice); }}
//
// AudioDevices.cpp
// interface/src/scripting
//
// Created by Zach Pomerantz on 28/5/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <map>
#include <shared/QtHelpers.h>
#include "AudioDevices.h"
#include "Application.h"
#include "AudioClient.h"
#include "Audio.h"
#include "UserActivityLogger.h"
using namespace scripting;
static Setting::Handle<QString> desktopInputDeviceSetting { QStringList { Audio::AUDIO, Audio::DESKTOP, "INPUT" }};
static Setting::Handle<QString> desktopOutputDeviceSetting { QStringList { Audio::AUDIO, Audio::DESKTOP, "OUTPUT" }};
static Setting::Handle<QString> hmdInputDeviceSetting { QStringList { Audio::AUDIO, Audio::HMD, "INPUT" }};
static Setting::Handle<QString> hmdOutputDeviceSetting { QStringList { Audio::AUDIO, Audio::HMD, "OUTPUT" }};
Setting::Handle<QString>& getSetting(bool contextIsHMD, QAudio::Mode mode) {
if (mode == QAudio::AudioInput) {
return contextIsHMD ? hmdInputDeviceSetting : desktopInputDeviceSetting;
} else { // if (mode == QAudio::AudioOutput)
return contextIsHMD ? hmdOutputDeviceSetting : desktopOutputDeviceSetting;
}
}
QHash<int, QByteArray> AudioDeviceList::_roles {
{ Qt::DisplayRole, "display" },
{ Qt::CheckStateRole, "selected" },
{ Qt::UserRole, "info" }
};
Qt::ItemFlags AudioDeviceList::_flags { Qt::ItemIsSelectable | Qt::ItemIsEnabled };
QVariant AudioDeviceList::data(const QModelIndex& index, int role) const {
if (!index.isValid() || index.row() >= _devices.size()) {
return QVariant();
}
if (role == Qt::DisplayRole) {
return _devices.at(index.row()).display;
} else if (role == Qt::CheckStateRole) {
return _devices.at(index.row()).selected;
} else if (role == Qt::UserRole) {
return QVariant::fromValue<QAudioDeviceInfo>(_devices.at(index.row()).info);
} else {
return QVariant();
}
}
void AudioDeviceList::resetDevice(bool contextIsHMD, const QString& device) {
auto client = DependencyManager::get<AudioClient>().data();
auto deviceName = getSetting(contextIsHMD, _mode).get();
bool switchResult = false;
QMetaObject::invokeMethod(client, "switchAudioDevice", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, switchResult),
Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName));
// try to set to the default device for this mode
if (!switchResult) {
if (contextIsHMD) {
QString deviceName;
if (_mode == QAudio::AudioInput) {
deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioInDevice();
} else { // if (_mode == QAudio::AudioOutput)
deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice();
}
if (!deviceName.isNull()) {
QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName));
}
} else {
// use the system default
QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudio::Mode, _mode));
}
}
}
void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device) {
auto oldDevice = _selectedDevice;
_selectedDevice = device;
for (auto i = 0; i < _devices.size(); ++i) {
AudioDevice& device = _devices[i];
if (device.selected && device.info != _selectedDevice) {
device.selected = false;
} else if (device.info == _selectedDevice) {
device.selected = true;
}
}
emit deviceChanged(_selectedDevice);
emit dataChanged(createIndex(0, 0), createIndex(rowCount() - 1, 0));
}
void AudioDeviceList::onDevicesChanged(const QList<QAudioDeviceInfo>& devices) {
beginResetModel();
_devices.clear();
foreach(const QAudioDeviceInfo& deviceInfo, devices) {
AudioDevice device;
device.info = deviceInfo;
device.display = device.info.deviceName()
.replace("High Definition", "HD")
.remove("Device")
.replace(" )", ")");
device.selected = (device.info == _selectedDevice);
_devices.push_back(device);
}
endResetModel();
}
AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) {
auto client = DependencyManager::get<AudioClient>();
connect(client.data(), &AudioClient::deviceChanged, this, &AudioDevices::onDeviceChanged, Qt::QueuedConnection);
connect(client.data(), &AudioClient::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection);
// connections are made after client is initialized, so we must also fetch the devices
_inputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioInput));
_outputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioOutput));
_inputs.onDevicesChanged(client->getAudioDevices(QAudio::AudioInput));
_outputs.onDevicesChanged(client->getAudioDevices(QAudio::AudioOutput));
}
void AudioDevices::onContextChanged(const QString& context) {
auto input = getSetting(_contextIsHMD, QAudio::AudioInput).get();
auto output = getSetting(_contextIsHMD, QAudio::AudioOutput).get();
_inputs.resetDevice(_contextIsHMD, input);
_outputs.resetDevice(_contextIsHMD, output);
}
void AudioDevices::onDeviceSelected(QAudio::Mode mode, const QAudioDeviceInfo& device, const QAudioDeviceInfo& previousDevice) {
QString deviceName = device.isNull() ? QString() : device.deviceName();
auto& setting = getSetting(_contextIsHMD, mode);
// check for a previous device
auto wasDefault = setting.get().isNull();
// store the selected device
setting.set(deviceName);
// log the selected device
if (!device.isNull()) {
QJsonObject data;
const QString MODE = "audio_mode";
const QString INPUT = "INPUT";
const QString OUTPUT = "OUTPUT"; data[MODE] = mode == QAudio::AudioInput ? INPUT : OUTPUT;
const QString CONTEXT = "display_mode";
data[CONTEXT] = _contextIsHMD ? Audio::HMD : Audio::DESKTOP;
const QString DISPLAY = "display_device";
data[DISPLAY] = qApp->getActiveDisplayPlugin()->getName();
const QString DEVICE = "device";
const QString PREVIOUS_DEVICE = "previous_device";
const QString WAS_DEFAULT = "was_default";
data[DEVICE] = deviceName;
data[PREVIOUS_DEVICE] = previousDevice.deviceName();
data[WAS_DEFAULT] = wasDefault;
UserActivityLogger::getInstance().logAction("selected_audio_device", data);
}
}
void AudioDevices::onDeviceChanged(QAudio::Mode mode, const QAudioDeviceInfo& device) {
if (mode == QAudio::AudioInput) {
_inputs.onDeviceChanged(device);
} else { // if (mode == QAudio::AudioOutput)
_outputs.onDeviceChanged(device);
}
}
void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList<QAudioDeviceInfo>& devices) {
static std::once_flag once;
if (mode == QAudio::AudioInput) {
_inputs.onDevicesChanged(devices);
} else { // if (mode == QAudio::AudioOutput)
_outputs.onDevicesChanged(devices);
}
std::call_once(once, [&] { onContextChanged(QString()); });
}
void AudioDevices::chooseInputDevice(const QAudioDeviceInfo& device) {
auto client = DependencyManager::get<AudioClient>();
bool success = false;
QMetaObject::invokeMethod(client.data(), "switchAudioDevice",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, success),
Q_ARG(QAudio::Mode, QAudio::AudioInput),
Q_ARG(const QAudioDeviceInfo&, device));
if (success) {
onDeviceSelected(QAudio::AudioInput, device, _inputs._selectedDevice);
}
}
void AudioDevices::chooseOutputDevice(const QAudioDeviceInfo& device) {
auto client = DependencyManager::get<AudioClient>();
bool success = false;
QMetaObject::invokeMethod(client.data(), "switchAudioDevice",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, success),
Q_ARG(QAudio::Mode, QAudio::AudioOutput),
Q_ARG(const QAudioDeviceInfo&, device));
if (success) {
onDeviceSelected(QAudio::AudioOutput, device, _outputs._selectedDevice);
}
}