From d18ac6dd498991b0fd16cfa7a6e0664ec705030c Mon Sep 17 00:00:00 2001 From: Vladyslav Stelmakhovskyi Date: Fri, 28 Apr 2017 23:02:28 +0200 Subject: [PATCH] Reimplemented Tablet Audio screen using C++ model --- interface/resources/qml/hifi/Audio.qml | 83 ++++++------- .../qml/hifi/components/AudioCheckbox.qml | 2 + .../AudioDeviceScriptingInterface.cpp | 115 +++++++++++++++++- .../scripting/AudioDeviceScriptingInterface.h | 33 ++++- libraries/audio-client/src/AudioClient.cpp | 5 +- libraries/audio-client/src/AudioClient.h | 3 + scripts/system/selectAudioDevice.js | 15 ++- 7 files changed, 208 insertions(+), 48 deletions(-) diff --git a/interface/resources/qml/hifi/Audio.qml b/interface/resources/qml/hifi/Audio.qml index d0c3122100..adb0cd4cba 100644 --- a/interface/resources/qml/hifi/Audio.qml +++ b/interface/resources/qml/hifi/Audio.qml @@ -35,11 +35,6 @@ Rectangle { property string title: "Audio Options" signal sendToScript(var message); - //set models after Components is shown - Component.onCompleted: { - refreshTimer.start() - refreshTimerOutput.start() - } Component { id: separator @@ -138,30 +133,34 @@ Rectangle { } ListView { - Timer { - id: refreshTimer - interval: 1 - repeat: false - onTriggered: { - //refresh model - inputAudioListView.model = undefined - inputAudioListView.model = AudioDevice.inputAudioDevices - } - } id: inputAudioListView anchors { left: parent.left; right: parent.right; leftMargin: 70 } height: 125 - spacing: 16 + spacing: 0 clip: true snapMode: ListView.SnapToItem - delegate: AudioCheckbox { + model: AudioDevice + delegate: Item { width: parent.width - checkbox.checked: (modelData === AudioDevice.getInputDevice()) - text.text: modelData - onCheckBoxClicked: { - if (checked) { - AudioDevice.setInputDevice(modelData) - refreshTimer.start() + visible: devicemode === 0 + height: visible ? 36 : 0 + + AudioCheckbox { + id: cbin + anchors.verticalCenter: parent.verticalCenter + Binding { + target: cbin.checkbox + property: 'checked' + value: devicechecked + } + + width: parent.width + cbchecked: devicechecked + text.text: devicename + onCheckBoxClicked: { + if (checked) { + AudioDevice.setInputDeviceAsync(devicename) + } } } } @@ -191,31 +190,33 @@ Rectangle { text: qsTr("CHOOSE OUTPUT DEVICE") } } + ListView { id: outputAudioListView - Timer { - id: refreshTimerOutput - interval: 1 - repeat: false - onTriggered: { - //refresh model - outputAudioListView.model = undefined - outputAudioListView.model = AudioDevice.outputAudioDevices - } - } anchors { left: parent.left; right: parent.right; leftMargin: 70 } height: 250 - spacing: 16 + spacing: 0 clip: true snapMode: ListView.SnapToItem - delegate: AudioCheckbox { + model: AudioDevice + delegate: Item { width: parent.width - checkbox.checked: (modelData === AudioDevice.getOutputDevice()) - text.text: modelData - onCheckBoxClicked: { - if (checked) { - AudioDevice.setOutputDevice(modelData) - refreshTimerOutput.start() + visible: devicemode === 1 + height: visible ? 36 : 0 + AudioCheckbox { + id: cbout + width: parent.width + anchors.verticalCenter: parent.verticalCenter + Binding { + target: cbout.checkbox + property: 'checked' + value: devicechecked + } + text.text: devicename + onCheckBoxClicked: { + if (checked) { + AudioDevice.setOutputDeviceAsync(devicename) + } } } } diff --git a/interface/resources/qml/hifi/components/AudioCheckbox.qml b/interface/resources/qml/hifi/components/AudioCheckbox.qml index a8e0441e0a..b23c9b42fe 100644 --- a/interface/resources/qml/hifi/components/AudioCheckbox.qml +++ b/interface/resources/qml/hifi/components/AudioCheckbox.qml @@ -8,6 +8,7 @@ Row { id: row spacing: 16 property alias checkbox: cb + property alias cbchecked: cb.checked property alias text: txt signal checkBoxClicked(bool checked) @@ -17,6 +18,7 @@ Row { colorScheme: hifi.colorSchemes.dark anchors.verticalCenter: parent.verticalCenter onClicked: checkBoxClicked(cb.checked) + onCheckedChanged: console.log("checked", checked, "device ",txt.text) } RalewayBold { id: txt diff --git a/interface/src/scripting/AudioDeviceScriptingInterface.cpp b/interface/src/scripting/AudioDeviceScriptingInterface.cpp index cbb08c0af0..4cec75074f 100644 --- a/interface/src/scripting/AudioDeviceScriptingInterface.cpp +++ b/interface/src/scripting/AudioDeviceScriptingInterface.cpp @@ -33,11 +33,17 @@ bool AudioDeviceScriptingInterface::muted() return getMuted(); } -AudioDeviceScriptingInterface::AudioDeviceScriptingInterface() { +AudioDeviceScriptingInterface::AudioDeviceScriptingInterface(): QAbstractListModel(nullptr) { connect(DependencyManager::get().data(), &AudioClient::muteToggled, this, &AudioDeviceScriptingInterface::muteToggled); connect(DependencyManager::get().data(), &AudioClient::deviceChanged, - this, &AudioDeviceScriptingInterface::deviceChanged); + this, &AudioDeviceScriptingInterface::onDeviceChanged, Qt::QueuedConnection); + connect(DependencyManager::get().data(), &AudioClient::currentInputDeviceChanged, + this, &AudioDeviceScriptingInterface::onCurrentInputDeviceChanged, Qt::QueuedConnection); + connect(DependencyManager::get().data(), &AudioClient::currentOutputDeviceChanged, + this, &AudioDeviceScriptingInterface::onCurrentOutputDeviceChanged, Qt::QueuedConnection); + //fill up model + onDeviceChanged(); } bool AudioDeviceScriptingInterface::setInputDevice(const QString& deviceName) { @@ -51,13 +57,27 @@ bool AudioDeviceScriptingInterface::setInputDevice(const QString& deviceName) { bool AudioDeviceScriptingInterface::setOutputDevice(const QString& deviceName) { bool result; + QTime a; a.start(); QMetaObject::invokeMethod(DependencyManager::get().data(), "switchOutputToAudioDevice", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(const QString&, deviceName)); + qDebug() << "switching to" << deviceName << "elapsed" << a.elapsed(); return result; } +void AudioDeviceScriptingInterface::setInputDeviceAsync(const QString& deviceName) { + QMetaObject::invokeMethod(DependencyManager::get().data(), "switchInputToAudioDevice", + Qt::QueuedConnection, + Q_ARG(const QString&, deviceName)); +} + +void AudioDeviceScriptingInterface::setOutputDeviceAsync(const QString& deviceName) { + QMetaObject::invokeMethod(DependencyManager::get().data(), "switchOutputToAudioDevice", + Qt::QueuedConnection, + Q_ARG(const QString&, deviceName)); +} + QString AudioDeviceScriptingInterface::getInputDevice() { return DependencyManager::get()->getDeviceName(QAudio::AudioInput); } @@ -116,3 +136,94 @@ void AudioDeviceScriptingInterface::setMuted(bool muted) bool AudioDeviceScriptingInterface::getMuted() { return DependencyManager::get()->isMuted(); } + +QVariant AudioDeviceScriptingInterface::data(const QModelIndex& index, int role) const { + //sanity + if (!index.isValid() || index.row() >= _devices.size()) + return QVariant(); + + + if (role == Qt::DisplayRole || role == DisplayNameRole) { + return _devices.at(index.row()).name; + } else if (role == SelectedRole) { + return _devices.at(index.row()).selected; + } else if (role == AudioModeRole) { + return (int)_devices.at(index.row()).mode; + } + return QVariant(); +} + +int AudioDeviceScriptingInterface::rowCount(const QModelIndex& parent) const { + Q_UNUSED(parent) + return _devices.size(); +} + +QHash AudioDeviceScriptingInterface::roleNames() const { + QHash roles; + roles.insert(DisplayNameRole, "devicename"); + roles.insert(SelectedRole, "devicechecked"); + roles.insert(AudioModeRole, "devicemode"); + return roles; +} + +void AudioDeviceScriptingInterface::onDeviceChanged() +{ + beginResetModel(); + _devices.clear(); + const QString &outDevice = getOutputDevice(); + for (QString name: getOutputDevices()) { + AudioDeviceInfo di; + di.name = name; + di.selected = (name == outDevice); + di.mode = QAudio::AudioOutput; + _devices.append(di); + } + const QString &inDevice = getInputDevice(); + for (QString name: getInputDevices()) { + AudioDeviceInfo di; + di.name = name; + di.selected = (name == inDevice); + di.mode = QAudio::AudioInput; + _devices.append(di); + } + + endResetModel(); + emit deviceChanged(); +} + +void AudioDeviceScriptingInterface::onCurrentInputDeviceChanged() +{ + currentDeviceUpdate(getInputDevice(), QAudio::AudioInput); + emit currentInputDeviceChanged(); +} + +void AudioDeviceScriptingInterface::onCurrentOutputDeviceChanged() +{ + currentDeviceUpdate(getOutputDevice(), QAudio::AudioOutput); + emit currentOutputDeviceChanged(); +} + +void AudioDeviceScriptingInterface::currentDeviceUpdate(const QString &name, QAudio::Mode mode) +{ + QVector role; + role.append(SelectedRole); + + qDebug() << "device update" << name << mode; + + for (int i = 0; i < _devices.size(); i++) { + AudioDeviceInfo di = _devices.at(i); + if (di.mode != mode) + continue; + if (di.selected && di.name != name ) { + di.selected = false; + _devices[i] = di; + emit dataChanged(index(i, 0), index(i, 0), role); + } + if (di.name == name) { + di.selected = true; + _devices[i] = di; + emit dataChanged(index(i, 0), index(i, 0), role); + qDebug() << "device updated" << di.name << i; + } + } +} diff --git a/interface/src/scripting/AudioDeviceScriptingInterface.h b/interface/src/scripting/AudioDeviceScriptingInterface.h index 4d1d47dcba..cadbe88d4d 100644 --- a/interface/src/scripting/AudioDeviceScriptingInterface.h +++ b/interface/src/scripting/AudioDeviceScriptingInterface.h @@ -15,10 +15,18 @@ #include #include #include +#include +#include class AudioEffectOptions; -class AudioDeviceScriptingInterface : public QObject { +struct AudioDeviceInfo { + QString name; + bool selected; + QAudio::Mode mode; +}; + +class AudioDeviceScriptingInterface : public QAbstractListModel { Q_OBJECT Q_PROPERTY(QStringList inputAudioDevices READ inputAudioDevices NOTIFY inputAudioDevicesChanged) @@ -32,6 +40,22 @@ public: QStringList outputAudioDevices() const; bool muted(); + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QHash roleNames() const override; + + enum Roles { + DisplayNameRole = Qt::UserRole, + SelectedRole, + AudioModeRole + }; + +private slots: + void onDeviceChanged(); + void onCurrentInputDeviceChanged(); + void onCurrentOutputDeviceChanged(); + void currentDeviceUpdate(const QString &name, QAudio::Mode mode); + public slots: bool setInputDevice(const QString& deviceName); bool setOutputDevice(const QString& deviceName); @@ -55,15 +79,22 @@ public slots: void setMuted(bool muted); + void setInputDeviceAsync(const QString &deviceName); + void setOutputDeviceAsync(const QString &deviceName); private: AudioDeviceScriptingInterface(); signals: void muteToggled(); void deviceChanged(); + void currentInputDeviceChanged(); + void currentOutputDeviceChanged(); void mutedChanged(bool muted); void inputAudioDevicesChanged(QStringList inputAudioDevices); void outputAudioDevicesChanged(QStringList outputAudioDevices); + +private: + QVector _devices; }; #endif // hifi_AudioDeviceScriptingInterface_h diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index b684aac89c..6e0feb3b20 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -769,7 +769,8 @@ QString AudioClient::getDefaultDeviceName(QAudio::Mode mode) { QVector AudioClient::getDeviceNames(QAudio::Mode mode) { QVector deviceNames; - foreach(QAudioDeviceInfo audioDevice, getAvailableDevices(mode)) { + const QList &availableDevice = getAvailableDevices(mode); + foreach(const QAudioDeviceInfo &audioDevice, availableDevice) { deviceNames << audioDevice.deviceName().trimmed(); } return deviceNames; @@ -1371,6 +1372,7 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceIn if (!inputDeviceInfo.isNull()) { qCDebug(audioclient) << "The audio input device " << inputDeviceInfo.deviceName() << "is available."; _inputAudioDeviceName = inputDeviceInfo.deviceName().trimmed(); + emit currentInputDeviceChanged(); if (adjustedFormatForAudioDevice(inputDeviceInfo, _desiredInputFormat, _inputFormat)) { qCDebug(audioclient) << "The format to be used for audio input is" << _inputFormat; @@ -1488,6 +1490,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice if (!outputDeviceInfo.isNull()) { qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << "is available."; _outputAudioDeviceName = outputDeviceInfo.deviceName().trimmed(); + emit currentOutputDeviceChanged(); if (adjustedFormatForAudioDevice(outputDeviceInfo, _desiredOutputFormat, _outputFormat)) { qCDebug(audioclient) << "The format to be used for audio output is" << _outputFormat; diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 139749e8e8..acd96c395e 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -240,6 +240,9 @@ signals: void muteEnvironmentRequested(glm::vec3 position, float radius); + void currentOutputDeviceChanged(); + void currentInputDeviceChanged(); + protected: AudioClient(); ~AudioClient(); diff --git a/scripts/system/selectAudioDevice.js b/scripts/system/selectAudioDevice.js index 2dd426932f..61862ad67b 100644 --- a/scripts/system/selectAudioDevice.js +++ b/scripts/system/selectAudioDevice.js @@ -64,13 +64,14 @@ function setupAudioMenus() { // Setup audio input devices Menu.addSeparator("Audio", "Input Audio Device"); var inputDevices = AudioDevice.getInputDevices(); + var currentInputDevice = AudioDevice.getInputDevice() for (var i = 0; i < inputDevices.length; i++) { var audioDeviceMenuString = "Use " + inputDevices[i] + " for Input"; Menu.addMenuItem({ menuName: "Audio", menuItemName: audioDeviceMenuString, isCheckable: true, - isChecked: inputDevices[i] == AudioDevice.getInputDevice() + isChecked: inputDevices[i] == currentInputDevice }); audioDevicesList.push(audioDeviceMenuString); } @@ -78,13 +79,14 @@ function setupAudioMenus() { // Setup audio output devices Menu.addSeparator("Audio", "Output Audio Device"); var outputDevices = AudioDevice.getOutputDevices(); + var currentOutputDevice = AudioDevice.getOutputDevice() for (var i = 0; i < outputDevices.length; i++) { var audioDeviceMenuString = "Use " + outputDevices[i] + " for Output"; Menu.addMenuItem({ menuName: "Audio", menuItemName: audioDeviceMenuString, isCheckable: true, - isChecked: outputDevices[i] == AudioDevice.getOutputDevice() + isChecked: outputDevices[i] == currentOutputDevice }); audioDevicesList.push(audioDeviceMenuString); } @@ -133,6 +135,11 @@ function onMenuEvent(audioDeviceMenuString) { } } +function onCurrentDeviceChanged() { + debug("System audio device switched. "); + setupAudioMenus() +} + function switchAudioDevice(audioDeviceMenuString) { // if the device is not plugged in, short-circuit if (!~audioDevicesList.indexOf(audioDeviceMenuString)) { @@ -254,7 +261,9 @@ function checkHMDAudio() { // Wait for the C++ systems to fire up before trying to do anything with audio devices Script.setTimeout(function () { debug("Connecting deviceChanged(), displayModeChanged(), and switchAudioDevice()..."); - AudioDevice.deviceChanged.connect(onDevicechanged); +// AudioDevice.deviceChanged.connect(onDevicechanged); +// AudioDevice.currentInputDeviceChanged.connect(onCurrentDeviceChanged); +// AudioDevice.currentOutputDeviceChanged.connect(onCurrentDeviceChanged); HMD.displayModeChanged.connect(checkHMDAudio); Menu.menuItemEvent.connect(onMenuEvent); debug("Setting up Audio I/O menu for the first time...");