diff --git a/tools/chatgui/src/stream_publish_window.cpp b/tools/chatgui/src/stream_publish_window.cpp index 70cb6646d..fdcda8f03 100644 --- a/tools/chatgui/src/stream_publish_window.cpp +++ b/tools/chatgui/src/stream_publish_window.cpp @@ -2,6 +2,7 @@ #include "videoplayer_window.h" #include "capture_controls.h" #include "sound_manager.h" +#include "../transport/gui_bridge.h" #include "debug_config.h" #include #include @@ -157,7 +158,9 @@ StreamPublishWindow::StreamPublishWindow(StreamPublishEngine* engine, uint64_t g m_cursor->setObjectName(QStringLiteral("streamShowCursor")); form->addRow(m_cursor); m_noAudio = addOption(m_microphone, text("No audio"), QVariant()); addOption(m_microphone, text("Default microphone"), QString()); select(m_microphone, QString()); - for (const auto& device : SoundManager::enumerateCaptureDevices()) addOption(m_microphone, device.name, device.id); + refreshMicrophones(); + connect(m_microphone, &QMenu::aboutToShow, this, &StreamPublishWindow::refreshMicrophones); + connect(SoundManager::instance(), &SoundManager::devicesChanged, this, [this] { refreshMicrophones(); synchronize(); }); m_inputChannels = new QLabel(settings); form->addRow(text("Audio input"), m_inputChannels); for (int qp : {0, 18, 23, 28, 35, 40}) addOption(m_maxQp, qp ? QString::number(qp) : text("Automatic"), qp); select(m_maxQp, 28); @@ -190,8 +193,23 @@ StreamPublishWindow::StreamPublishWindow(StreamPublishEngine* engine, uint64_t g connect(m_resolution, &QMenu::triggered, this, [this] { populateFrameRates(m_fps, m_modes, value(m_resolution).toSize(), value(m_fps).toInt()); apply(); }); - for (auto* menu : {m_fps, m_codec, m_bitrate, m_maxQp, m_rotation, m_microphone}) + for (auto* menu : {m_fps, m_codec, m_bitrate, m_maxQp, m_rotation}) connect(menu, &QMenu::triggered, this, &StreamPublishWindow::apply); + connect(m_microphone, &QMenu::triggered, this, [this](QAction* action) { + if (m_updating || !action->isCheckable()) return; + if (action != m_noAudio) { + auto* sound = SoundManager::instance(); + const QString id = action->data().toString(); + if (id != sound->captureDevice()) { + if (gui_bridge_post_setting(GUI_SETTING_UI_STATE, "audio_input_id", id.toUtf8().constData()) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "desktop microphone selection could not be saved input=%s", qPrintable(id)); + m_status->setText(text("Failed to select device")); refreshMicrophones(); synchronize(); return; + } + sound->setDevices(sound->playbackDevice(), id); + } + } + synchronize(); saveSettings(); + }); for (auto* menu : {m_codec, m_maxQp}) connect(menu, &QMenu::triggered, this, [this] { if (!m_updating) synchronize(); }); connect(m_cursor, &QCheckBox::toggled, this, &StreamPublishWindow::apply); connect(m_adaptiveQp, &QCheckBox::toggled, this, &StreamPublishWindow::apply); @@ -283,8 +301,7 @@ void StreamPublishWindow::restoreSettings() { restore(m_maxQp, maxQp, "max_qp"); m_adaptiveQp->setChecked(m_settings.value("adaptive_qp").toBool()); restore(m_rotation, m_settings.value("rotation").toInt(), "rotation"); m_cursor->setChecked(m_settings.value("show_cursor").toBool(true)); - if (m_settings.value("audio").toBool(true)) restore(m_microphone, m_settings.value("microphone").toString(), "microphone"); - else m_noAudio->setChecked(true); + if (!m_settings.value("audio").toBool(true)) m_noAudio->setChecked(true); m_updating = false; DEBUG_INFO(DEBUG_CATEGORY_STREAM, "desktop capture settings restored: %s", QJsonDocument(m_settings).toJson(QJsonDocument::Compact).constData()); } @@ -303,7 +320,7 @@ void StreamPublishWindow::saveSettings() { } if (selected(m_codec)) saved["codec"] = config.video.codec; saved["bitrate"] = value(m_bitrate).toInt(); saved["rotation"] = config.video.rotation; - saved["audio"] = config.audio; saved["microphone"] = config.microphone; + saved["audio"] = config.audio; saved.remove("microphone"); saved["muted"] = m_audioMuted; saved["show_cursor"] = config.showCursor; saved["max_qp"] = value(m_maxQp).toInt(); saved["adaptive_qp"] = m_adaptiveQp->isChecked(); @@ -379,6 +396,19 @@ void StreamPublishWindow::refreshModes(const StreamPublishConfig& previous, bool source.kind == StreamVideoSource::Screen && mode.size == m_modes.front().size); } +void StreamPublishWindow::refreshMicrophones() { + QScopedValueRollback updating(m_updating, true); + const bool audio = m_engine->active() ? m_engine->config().audio : selected(m_microphone) != m_noAudio; + const QString input = SoundManager::instance()->captureDevice(); + clearOptions(m_microphone, "Audio source"); + m_noAudio = addOption(m_microphone, text("No audio"), QVariant()); + m_noAudio->setEnabled(!m_engine->active()); + addOption(m_microphone, text("Default microphone"), QString()); + for (const auto& device : SoundManager::enumerateCaptureDevices()) addOption(m_microphone, device.name, device.id); + if (!option(m_microphone, input)) addOption(m_microphone, text("Selected microphone unavailable"), input); + if (audio) select(m_microphone, input); else m_noAudio->setChecked(true); +} + void StreamPublishWindow::refreshCodecs() { QScopedValueRollback updating(m_updating, true); int previous = selected(m_codec) ? value(m_codec).toInt() : m_settings.value("codec").toInt(); @@ -405,7 +435,7 @@ StreamPublishConfig StreamPublishWindow::selection() const { config.adaptiveQp = config.video.maxQp >= 0 && m_adaptiveQp->isChecked(); } config.showCursor = m_cursor->isChecked(); - config.microphone = value(m_microphone).toString(); config.audio = selected(m_microphone) != m_noAudio; + config.audio = selected(m_microphone) != m_noAudio; config.startMuted = m_audioMuted; return config; } void StreamPublishWindow::apply() { @@ -422,7 +452,8 @@ void StreamPublishWindow::synchronize() { && selected(m_source) && selected(m_resolution) && selected(m_fps) && selected(m_codec))); for (auto* menu : {m_source, m_resolution, m_fps, m_codec, m_bitrate, m_rotation}) menu->menuAction()->setEnabled(!active || m_engine->running()); - m_microphone->menuAction()->setEnabled(!active); + m_microphone->menuAction()->setEnabled(!active || (m_engine->running() && m_engine->config().audio)); + m_noAudio->setEnabled(!active); if (active) m_audioMuted = m_engine->muted(); bool audio = active ? m_engine->config().audio : selection().audio; m_sound->setEnabled(audio && (!active || m_engine->running())); @@ -443,7 +474,7 @@ void StreamPublishWindow::synchronize() { m_adaptiveQp->setChecked(config.adaptiveQp); } select(m_rotation, config.video.rotation); - if (config.audio) select(m_microphone, config.microphone); else m_noAudio->setChecked(true); + if (config.audio) select(m_microphone, SoundManager::instance()->captureDevice()); else m_noAudio->setChecked(true); m_cursor->setChecked(config.showCursor); } m_cursor->setVisible(StreamPublishEngine::cursorSupported(selection().source)); diff --git a/tools/chatgui/src/stream_publish_window.h b/tools/chatgui/src/stream_publish_window.h index 2622ca98c..7b281e364 100644 --- a/tools/chatgui/src/stream_publish_window.h +++ b/tools/chatgui/src/stream_publish_window.h @@ -51,6 +51,7 @@ private: void refreshModes(const StreamPublishConfig& previous, bool maximum = false); void updateMenuButtons(); // Обновляет иконки со значениями и подсказки верхнего ряда после выбора/синхронизации. void refreshCodecs(); // Перечитывает engine.codecs(), сохраняет текущий/saved codec, недоступный заменяет первым. + void refreshMicrophones(); // Перечитывает устройства и общий с рацией выбор; «Без звука» относится только к трансляции. void synchronize(); // Читает engine config/state без apply; настраивает controls и ограничения изменения audio/cursor/QP. void apply(); // Для выбранного режима: reconfigure только active-engine, затем сохранение выбора; не во время m_updating. void restoreSettings(); // До start восстанавливает доступные значения JSON; некорректные/отсутствующие диагностируются и заменяются.