diff --git a/docs/sphinx/reference-sources.rst b/docs/sphinx/reference-sources.rst index a6e7aed33aa58b..f27b507e32e4dc 100644 --- a/docs/sphinx/reference-sources.rst +++ b/docs/sphinx/reference-sources.rst @@ -599,6 +599,10 @@ The following signals are defined for every source type: Called when the source is muted/unmuted. +**monitor_enable** (ptr source, bool enabled) + + Called when the source audio monitor is enabled/disabled. + **push_to_mute_changed** (ptr source, bool enabled) Called when push-to-mute has been enabled/disabled. @@ -1166,6 +1170,13 @@ General Source Functions --------------------- +.. function:: bool obs_source_monitor_enabled(const obs_source_t *source) + void obs_source_set_monitor_enabled(obs_source_t *source, bool muted) + + Sets/gets whether the source's audio monitor is enabled. + +--------------------- + .. function:: enum speaker_layout obs_source_get_speaker_layout(obs_source_t *source) Gets the current speaker layout. diff --git a/frontend/components/OBSAdvAudioCtrl.cpp b/frontend/components/OBSAdvAudioCtrl.cpp index 33a6385aecbb44..4a3a72e1187b9b 100644 --- a/frontend/components/OBSAdvAudioCtrl.cpp +++ b/frontend/components/OBSAdvAudioCtrl.cpp @@ -39,8 +39,6 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *, obs_source_t *source_) : source( percent = new QSpinBox(); forceMono = new QCheckBox(); balance = new BalanceSlider(); - if (obs_audio_monitoring_available()) - monitoringType = new QComboBox(); syncOffset = new QSpinBox(); mixer1 = new QCheckBox(); mixer2 = new QCheckBox(); @@ -56,8 +54,6 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *, obs_source_t *source_) : source( sigs.emplace_back(handler, "volume", OBSSourceVolumeChanged, this); sigs.emplace_back(handler, "audio_sync", OBSSourceSyncChanged, this); sigs.emplace_back(handler, "update_flags", OBSSourceFlagsChanged, this); - if (obs_audio_monitoring_available()) - sigs.emplace_back(handler, "audio_monitoring", OBSSourceMonitoringTypeChanged, this); sigs.emplace_back(handler, "audio_mixers", OBSSourceMixersChanged, this); sigs.emplace_back(handler, "audio_balance", OBSSourceBalanceChanged, this); sigs.emplace_back(handler, "rename", OBSSourceRenamed, this); @@ -147,20 +143,6 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *, obs_source_t *source_) : source( syncOffset->setFixedWidth(100); syncOffset->setAccessibleName(QTStr("Basic.AdvAudio.SyncOffsetSource").arg(sourceName)); - int idx; - if (obs_audio_monitoring_available()) { - monitoringType->addItem(QTStr("Basic.AdvAudio.Monitoring.None"), (int)OBS_MONITORING_TYPE_NONE); - monitoringType->addItem(QTStr("Basic.AdvAudio.Monitoring.MonitorOnly"), - (int)OBS_MONITORING_TYPE_MONITOR_ONLY); - monitoringType->addItem(QTStr("Basic.AdvAudio.Monitoring.Both"), - (int)OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT); - int mt = (int)obs_source_get_monitoring_type(source); - idx = monitoringType->findData(mt); - monitoringType->setCurrentIndex(idx); - monitoringType->setAccessibleName(QTStr("Basic.AdvAudio.MonitoringSource").arg(sourceName)); - monitoringType->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); - } - mixer1->setText("1"); mixer1->setChecked(mixers & (1 << 0)); mixer1->setAccessibleName(QTStr("Basic.Settings.Output.Adv.Audio.Track1")); @@ -203,8 +185,6 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *, obs_source_t *source_) : source( connect(balance, &BalanceSlider::valueChanged, this, &OBSAdvAudioCtrl::balanceChanged); connect(balance, &BalanceSlider::doubleClicked, this, &OBSAdvAudioCtrl::ResetBalance); connect(syncOffset, &QSpinBox::valueChanged, this, &OBSAdvAudioCtrl::syncOffsetChanged); - if (obs_audio_monitoring_available()) - connect(monitoringType, &QComboBox::currentIndexChanged, this, &OBSAdvAudioCtrl::monitoringTypeChanged); auto connectMixer = [this](QCheckBox *mixer, int num) { connect(mixer, &QCheckBox::clicked, [this, num](bool checked) { setMixer(source, num, checked); }); @@ -228,8 +208,6 @@ OBSAdvAudioCtrl::~OBSAdvAudioCtrl() forceMono->deleteLater(); balanceContainer->deleteLater(); syncOffset->deleteLater(); - if (obs_audio_monitoring_available()) - monitoringType->deleteLater(); mixerContainer->deleteLater(); } @@ -245,8 +223,6 @@ void OBSAdvAudioCtrl::ShowAudioControl(QGridLayout *layout) layout->addWidget(forceMono, lastRow, idx++); layout->addWidget(balanceContainer, lastRow, idx++); layout->addWidget(syncOffset, lastRow, idx++); - if (obs_audio_monitoring_available()) - layout->addWidget(monitoringType, lastRow, idx++); layout->addWidget(mixerContainer, lastRow, idx++); layout->layout()->setAlignment(mixerContainer, Qt::AlignVCenter); layout->setHorizontalSpacing(15); @@ -283,13 +259,6 @@ void OBSAdvAudioCtrl::OBSSourceSyncChanged(void *param, calldata_t *calldata) QMetaObject::invokeMethod(static_cast(param), "SourceSyncChanged", Q_ARG(int64_t, offset)); } -void OBSAdvAudioCtrl::OBSSourceMonitoringTypeChanged(void *param, calldata_t *calldata) -{ - int type = calldata_int(calldata, "type"); - QMetaObject::invokeMethod(static_cast(param), "SourceMonitoringTypeChanged", - Q_ARG(int, type)); -} - void OBSAdvAudioCtrl::OBSSourceMixersChanged(void *param, calldata_t *calldata) { uint32_t mixers = (uint32_t)calldata_int(calldata, "mixers"); @@ -361,14 +330,6 @@ void OBSAdvAudioCtrl::SourceSyncChanged(int64_t offset) syncOffset->blockSignals(false); } -void OBSAdvAudioCtrl::SourceMonitoringTypeChanged(int type) -{ - int idx = monitoringType->findData(type); - monitoringType->blockSignals(true); - monitoringType->setCurrentIndex(idx); - monitoringType->blockSignals(false); -} - void OBSAdvAudioCtrl::SourceMixersChanged(uint32_t mixers) { setCheckboxState(mixer1, mixers & (1 << 0)); @@ -517,41 +478,6 @@ void OBSAdvAudioCtrl::syncOffsetChanged(int milliseconds) std::bind(undo_redo, std::placeholders::_1, val), uuid, uuid, true); } -void OBSAdvAudioCtrl::monitoringTypeChanged(int index) -{ - obs_monitoring_type prev = obs_source_get_monitoring_type(source); - - obs_monitoring_type mt = (obs_monitoring_type)monitoringType->itemData(index).toInt(); - obs_source_set_monitoring_type(source, mt); - - const char *type = nullptr; - - switch (mt) { - case OBS_MONITORING_TYPE_NONE: - type = "none"; - break; - case OBS_MONITORING_TYPE_MONITOR_ONLY: - type = "monitor only"; - break; - case OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT: - type = "monitor and output"; - break; - } - - const char *name = obs_source_get_name(source); - blog(LOG_INFO, "User changed audio monitoring for source '%s' to: %s", name ? name : "(null)", type); - - auto undo_redo = [](const std::string &uuid, obs_monitoring_type val) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str()); - obs_source_set_monitoring_type(source, val); - }; - - const char *uuid = obs_source_get_uuid(source); - OBSBasic::Get()->undo_s.add_action(QTStr("Undo.MonitoringType.Change").arg(name), - std::bind(undo_redo, std::placeholders::_1, prev), - std::bind(undo_redo, std::placeholders::_1, mt), uuid, uuid); -} - static inline void setMixer(obs_source_t *source, const int mixerIdx, const bool checked) { uint32_t mixers = obs_source_get_audio_mixers(source); diff --git a/frontend/components/OBSAdvAudioCtrl.hpp b/frontend/components/OBSAdvAudioCtrl.hpp index 6d12b3044b010f..a874eb5db943a2 100644 --- a/frontend/components/OBSAdvAudioCtrl.hpp +++ b/frontend/components/OBSAdvAudioCtrl.hpp @@ -40,7 +40,6 @@ class OBSAdvAudioCtrl : public QObject { QPointer labelL; QPointer labelR; QPointer syncOffset; - QPointer monitoringType; QPointer mixer1; QPointer mixer2; QPointer mixer3; @@ -55,7 +54,6 @@ class OBSAdvAudioCtrl : public QObject { static void OBSSourceFlagsChanged(void *param, calldata_t *calldata); static void OBSSourceVolumeChanged(void *param, calldata_t *calldata); static void OBSSourceSyncChanged(void *param, calldata_t *calldata); - static void OBSSourceMonitoringTypeChanged(void *param, calldata_t *calldata); static void OBSSourceMixersChanged(void *param, calldata_t *calldata); static void OBSSourceBalanceChanged(void *param, calldata_t *calldata); static void OBSSourceRenamed(void *param, calldata_t *calldata); @@ -75,7 +73,6 @@ public slots: void SourceFlagsChanged(uint32_t flags); void SourceVolumeChanged(float volume); void SourceSyncChanged(int64_t offset); - void SourceMonitoringTypeChanged(int type); void SourceMixersChanged(uint32_t mixers); void SourceBalanceChanged(int balance); void SetSourceName(QString newName); @@ -85,6 +82,5 @@ public slots: void downmixMonoChanged(bool checked); void balanceChanged(int val); void syncOffsetChanged(int milliseconds); - void monitoringTypeChanged(int index); void ResetBalance(); }; diff --git a/frontend/data/locale/en-US.ini b/frontend/data/locale/en-US.ini index c67f20b7d1f08c..ec3b8b79e4d31d 100644 --- a/frontend/data/locale/en-US.ini +++ b/frontend/data/locale/en-US.ini @@ -341,6 +341,8 @@ Undo.Transform.HCenter="Horizontal Center to Screen in '%1'" Undo.Volume.Change="Volume Change on '%1'" Undo.Volume.Mute="Mute '%1'" Undo.Volume.Unmute="Unmute '%1'" +Undo.Monitor.Enable="Enable Audio Monitor '%1'" +Undo.Monitor.Disable="Disable Audio Monitor '%1'" Undo.Balance.Change="Audio Balance Change on '%1'" Undo.SyncOffset.Change="Audio Sync Offset Change on '%1'" Undo.MonitoringType.Change="Change Audio Monitoring on '%1'" @@ -594,6 +596,7 @@ VolControl.Mute="Mute '%1'" VolControl.Properties="Properties for '%1'" VolControl.UnassignedWarning.Title="Unassigned Audio Source" VolControl.UnassignedWarning.Text="\"%1\" is not assigned to any audio tracks and it will not be audible in streams or recordings.\n\nTo assign an audio source to a track, open the Advanced Audio Properties via the right-click menu or the cog button in the mixer dock toolbar." +VolControl.Monitor="Set audio monitor for '%1'" # add scene dialog Basic.Main.AddSceneDlg.Title="Add Scene" diff --git a/frontend/data/themes/Dark/headphones-on.svg b/frontend/data/themes/Dark/headphones-on.svg new file mode 100644 index 00000000000000..3f22654271adfe --- /dev/null +++ b/frontend/data/themes/Dark/headphones-on.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/data/themes/Light/headphones-on.svg b/frontend/data/themes/Light/headphones-on.svg new file mode 100644 index 00000000000000..be661fe33889c2 --- /dev/null +++ b/frontend/data/themes/Light/headphones-on.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/data/themes/System.obt b/frontend/data/themes/System.obt index ed574a02bfb1ba..2e9ad4b80ecb33 100644 --- a/frontend/data/themes/System.obt +++ b/frontend/data/themes/System.obt @@ -100,6 +100,18 @@ image: url(:/settings/images/settings/audio.svg); } +.indicator-headphones { + outline: none; +} + +.indicator-headphones::indicator:checked { + image: url(:/res/images/headphones-on.svg); +} + +.indicator-headphones::indicator:unchecked { + image: url(:/res/images/headphones-off.svg); +} + .indicator-expand { background: transparent; outline: none; diff --git a/frontend/data/themes/Yami.obt b/frontend/data/themes/Yami.obt index 06199135f51ac4..0dfd6a120250f7 100644 --- a/frontend/data/themes/Yami.obt +++ b/frontend/data/themes/Yami.obt @@ -1834,6 +1834,71 @@ QTableView::indicator:unchecked:disabled { image: url(theme:Dark/settings/audio.svg); } +/* Headphone CheckBox */ + +.indicator-headphones { + outline: none; +} + +.indicator-headphones::indicator, +.indicator-headphones::indicator:unchecked { + width: var(--icon_base); + height: var(--icon_base); + background-color: var(--button_bg); + padding: var(--padding_base_border) var(--padding_base_border); + margin: 0px; + border: var(--highlight_width) solid var(--button_border); + border-radius: var(--border_radius); + icon-size: var(--icon_base); +} + +.indicator-headphones::indicator:hover, +.indicator-headphones::indicator:unchecked:hover { + background-color: var(--button_bg_hover); + padding: var(--padding_base_border) var(--padding_base_border); + margin: 0px; + border: var(--highlight_width) solid var(--button_border_hover); + icon-size: var(--icon_base); +} + +.indicator-headphones::indicator:pressed, +.indicator-headphones::indicator:pressed:hover { + background-color: var(--button_bg_down); + border-color: var(--button_border); +} + +.indicator-headphones::indicator:checked { + image: url(theme:Dark/headphones-on.svg); +} + +.indicator-headphones::indicator:unchecked { + image: url(:/res/images/headphones-off.svg); +} + +.indicator-headphones::indicator:unchecked:hover { + image: url(:/res/images/headphones-off.svg); +} + +.indicator-headphones::indicator:unchecked:focus { + image: url(:/res/images/headphones-off.svg); +} + +.indicator-headphones::indicator:checked:hover { + image: url(theme:Dark/headphones-on.svg); +} + +.indicator-headphones::indicator:checked:focus { + image: url(theme:Dark/headphones-on.svg); +} + +.indicator-headphones::indicator:checked:disabled { + image: url(theme:Dark/headphones-on.svg); +} + +.indicator-headphones::indicator:unchecked:disabled { + image: url(:/res/images/headphones-off.svg); +} + #hotkeyFilterReset { margin-top: 0px; } diff --git a/frontend/data/themes/Yami_Light.ovt b/frontend/data/themes/Yami_Light.ovt index 09bd53b1e8e0ad..4b82e1235a967c 100644 --- a/frontend/data/themes/Yami_Light.ovt +++ b/frontend/data/themes/Yami_Light.ovt @@ -245,6 +245,38 @@ QTableView::indicator:unchecked:disabled { image: url(theme:Light/settings/audio.svg); } +.indicator-headphones::indicator:checked { + image: url(theme:Light/headphones-on.svg); +} + +.indicator-headphones::indicator:unchecked { + image: url(:/res/images/headphones-off.svg); +} + +.indicator-headphones::indicator:unchecked:hover { + image: url(:/res/images/headphones-off.svg); +} + +.indicator-headphones::indicator:unchecked:focus { + image: url(:/res/images/headphones-off.svg); +} + +.indicator-headphones::indicator:checked:hover { + image: url(theme:Light/headphones-on.svg); +} + +.indicator-headphones::indicator:checked:focus { + image: url(theme:Light/headphones-on.svg); +} + +.indicator-headphones::indicator:checked:disabled { + image: url(theme:Light/headphones-on.svg); +} + +.indicator-headphones::indicator:unchecked:disabled { + image: url(:/res/images/headphones-off.svg); +} + .indicator-expand::indicator:checked, .indicator-expand::indicator:checked:hover { image: url(theme:Light/expand.svg); diff --git a/frontend/forms/OBSAdvAudio.ui b/frontend/forms/OBSAdvAudio.ui index b72c53fa8ff2c6..8490983d432cfe 100644 --- a/frontend/forms/OBSAdvAudio.ui +++ b/frontend/forms/OBSAdvAudio.ui @@ -17,7 +17,7 @@ - Qt::CustomContextMenu + Qt::ContextMenuPolicy::CustomContextMenu Basic.AdvAudio @@ -59,7 +59,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -87,7 +87,7 @@ - QAbstractScrollArea::AdjustToContents + QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents true @@ -95,10 +95,10 @@ - 0 + -212 0 - 1157 - 262 + 1288 + 264 @@ -139,93 +139,88 @@ 4 - - + + true - Basic.AdvAudio.Balance + Basic.AdvAudio.Name - - - - - 0 - 0 - - + + true - Basic.Stats.Status + Basic.AdvAudio.AudioTracks - - + + true - Basic.AdvAudio.Mono + Basic.AdvAudio.Balance - - + + true - Basic.AdvAudio.AudioTracks + Basic.AdvAudio.Mono - - + + true - Basic.AdvAudio.Name + Basic.AdvAudio.SyncOffset - - + + + + + 0 + 0 + + true - Basic.AdvAudio.SyncOffset + Basic.Stats.Status - - - - - true - - + + - Basic.AdvAudio.Monitoring + @@ -279,10 +274,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Minimum + QSizePolicy::Policy::Minimum @@ -297,19 +292,12 @@ - - - - - - - - Qt::Vertical + Qt::Orientation::Vertical diff --git a/frontend/forms/images/headphones-off.svg b/frontend/forms/images/headphones-off.svg new file mode 100644 index 00000000000000..80f4a904b7e3fc --- /dev/null +++ b/frontend/forms/images/headphones-off.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/forms/images/headphones-on.svg b/frontend/forms/images/headphones-on.svg new file mode 100644 index 00000000000000..24cf5f84db8645 --- /dev/null +++ b/frontend/forms/images/headphones-on.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/forms/obs.qrc b/frontend/forms/obs.qrc index 2faceae6830a8c..e88a1d3efa42d1 100644 --- a/frontend/forms/obs.qrc +++ b/frontend/forms/obs.qrc @@ -74,6 +74,8 @@ images/network-bad.svg images/network-disconnected.svg images/network-inactive.svg + images/headphones-on.svg + images/headphones-off.svg images/settings/output.svg diff --git a/frontend/widgets/VolControl.cpp b/frontend/widgets/VolControl.cpp index ea9ee5c6bc0c17..b3531270e2daaf 100644 --- a/frontend/widgets/VolControl.cpp +++ b/frontend/widgets/VolControl.cpp @@ -75,6 +75,14 @@ void VolControl::OBSVolumeMuted(void *data, calldata_t *calldata) QMetaObject::invokeMethod(volControl, "VolumeMuted", Q_ARG(bool, muted)); } +void VolControl::OBSMonitorEnabled(void *data, calldata_t *calldata) +{ + VolControl *volControl = static_cast(data); + bool enabled = calldata_bool(calldata, "enabled"); + + QMetaObject::invokeMethod(volControl, "MonitorEnabled", Q_ARG(bool, enabled)); +} + void VolControl::VolumeChanged() { slider->blockSignals(true); @@ -95,6 +103,12 @@ void VolControl::VolumeMuted(bool muted) volMeter->muted = muted || unassigned; } +void VolControl::MonitorEnabled(bool enabled) +{ + if (monitor && (monitor->isChecked() != enabled)) + monitor->setChecked(enabled); +} + void VolControl::OBSMixersOrMonitoringChanged(void *data, calldata_t *) { @@ -143,6 +157,24 @@ void VolControl::SetMuted(bool) std::bind(undo_redo, std::placeholders::_1, checked), uuid, uuid); } +void VolControl::SetMonitorEnabled(bool checked) +{ + bool prev = obs_source_monitor_enabled(source); + obs_source_set_monitor_enabled(source, checked); + + auto undo_redo = [](const std::string &uuid, bool val) { + OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str()); + obs_source_set_monitor_enabled(source, val); + }; + + QString text = QTStr(checked ? "Undo.Monitor.Enable" : "Undo.Monitor.Disable"); + + const char *name = obs_source_get_name(source); + const char *uuid = obs_source_get_uuid(source); + OBSBasic::Get()->undo_s.add_action(text.arg(name), std::bind(undo_redo, std::placeholders::_1, prev), + std::bind(undo_redo, std::placeholders::_1, checked), uuid, uuid); +} + void VolControl::SliderChanged(int vol) { float prev = obs_source_get_volume(source); @@ -211,6 +243,10 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) nameLabel = new OBSSourceLabel(source); volLabel = new QLabel(); mute = new MuteCheckBox(); + if (obs_audio_monitoring_available()) { + monitor = new QCheckBox(); + setClasses(monitor, "indicator-headphones"); + } volLabel->setObjectName("volLabel"); volLabel->setAlignment(Qt::AlignCenter); @@ -264,8 +300,8 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) controlLayout->setContentsMargins(0, 0, 0, 0); controlLayout->setSpacing(0); - // Add Headphone (audio monitoring) widget here controlLayout->addWidget(mute); + controlLayout->addWidget(monitor); if (showConfig) { controlLayout->addWidget(config); @@ -333,6 +369,7 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) } buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding)); buttonLayout->addWidget(mute); + buttonLayout->addWidget(monitor); controlLayout->addItem(buttonLayout); controlLayout->addWidget(meterFrame); @@ -358,13 +395,22 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) obs_fader_add_callback(obs_fader, OBSVolumeChanged, this); obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this); + if (obs_audio_monitoring_available()) { + monitor->setAccessibleName(QTStr("VolControl.Monitor").arg(sourceName)); + monitor->setChecked(obs_source_monitor_enabled(source)); + } + sigs.emplace_back(obs_source_get_signal_handler(source), "mute", OBSVolumeMuted, this); sigs.emplace_back(obs_source_get_signal_handler(source), "audio_mixers", OBSMixersOrMonitoringChanged, this); sigs.emplace_back(obs_source_get_signal_handler(source), "audio_monitoring", OBSMixersOrMonitoringChanged, this); + if (obs_audio_monitoring_available()) + sigs.emplace_back(obs_source_get_signal_handler(source), "monitor_enable", OBSMonitorEnabled, this); QWidget::connect(slider, &VolumeSlider::valueChanged, this, &VolControl::SliderChanged); QWidget::connect(mute, &MuteCheckBox::clicked, this, &VolControl::SetMuted); + if (obs_audio_monitoring_available()) + QWidget::connect(monitor, &QCheckBox::clicked, this, &VolControl::SetMonitorEnabled); obs_fader_attach_source(obs_fader, source); obs_volmeter_attach_source(obs_volmeter, source); diff --git a/frontend/widgets/VolControl.hpp b/frontend/widgets/VolControl.hpp index fe4ba8a203ac60..0c7a10677b1a59 100644 --- a/frontend/widgets/VolControl.hpp +++ b/frontend/widgets/VolControl.hpp @@ -8,6 +8,7 @@ class OBSSourceLabel; class VolumeMeter; class VolumeSlider; class MuteCheckBox; +class QCheckBox; class QLabel; class QPushButton; @@ -22,6 +23,7 @@ class VolControl : public QFrame { VolumeMeter *volMeter; VolumeSlider *slider; MuteCheckBox *mute; + QCheckBox *monitor; QPushButton *config = nullptr; float levelTotal; float levelCount; @@ -35,15 +37,18 @@ class VolControl : public QFrame { const float peak[MAX_AUDIO_CHANNELS], const float inputPeak[MAX_AUDIO_CHANNELS]); static void OBSVolumeMuted(void *data, calldata_t *calldata); static void OBSMixersOrMonitoringChanged(void *data, calldata_t *); + static void OBSMonitorEnabled(void *data, calldata_t *calldata); void EmitConfigClicked(); private slots: void VolumeChanged(); void VolumeMuted(bool muted); + void MonitorEnabled(bool enabled); void MixersOrMonitoringChanged(); void SetMuted(bool checked); + void SetMonitorEnabled(bool checked); void SliderChanged(int vol); void updateText(); diff --git a/libobs/obs-source.c b/libobs/obs-source.c index 31e70936e3a53a..3dcbbfe8e9b95d 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -112,6 +112,7 @@ static const char *source_signals[] = { "void media_previous(ptr source)", "void media_started(ptr source)", "void media_ended(ptr source)", + "void monitor_enable(ptr source, bool enabled)", NULL, }; @@ -1585,7 +1586,9 @@ static void source_output_audio_data(obs_source_t *source, const struct audio_da pthread_mutex_unlock(&source->audio_buf_mutex); - source_signal_audio_data(source, data, source_muted(source, os_time)); + source_signal_audio_data(source, data, + source_muted(source, os_time) && + source->monitoring_type != OBS_MONITORING_TYPE_MONITOR_ONLY); } enum convert_type { @@ -4956,11 +4959,31 @@ bool obs_source_muted(const obs_source_t *source) return obs_source_valid(source, "obs_source_muted") ? source->user_muted : false; } +static void set_mute_and_monitor(obs_source_t *source, bool mute, bool monitor) +{ + if (!source) + return; + + if (!monitor) { + obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_NONE); + } else { + if (mute) + obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_MONITOR_ONLY); + else + obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT); + } + + struct audio_action action = {.timestamp = os_gettime_ns(), .type = AUDIO_ACTION_MUTE, .set = mute}; + + pthread_mutex_lock(&source->audio_actions_mutex); + da_push_back(source->audio_actions, &action); + pthread_mutex_unlock(&source->audio_actions_mutex); +} + void obs_source_set_muted(obs_source_t *source, bool muted) { struct calldata data; uint8_t stack[128]; - struct audio_action action = {.timestamp = os_gettime_ns(), .type = AUDIO_ACTION_MUTE, .set = muted}; if (!obs_source_valid(source, "obs_source_set_muted")) return; @@ -4973,9 +4996,7 @@ void obs_source_set_muted(obs_source_t *source, bool muted) signal_handler_signal(source->context.signals, "mute", &data); - pthread_mutex_lock(&source->audio_actions_mutex); - da_push_back(source->audio_actions, &action); - pthread_mutex_unlock(&source->audio_actions_mutex); + set_mute_and_monitor(source, muted, obs_source_monitor_enabled(source)); } static void source_signal_push_to_changed(obs_source_t *source, const char *signal, bool enabled) @@ -5911,3 +5932,30 @@ obs_canvas_t *obs_source_get_canvas(const obs_source_t *source) { return obs_weak_canvas_get_canvas(source->canvas); } + +void obs_source_set_monitor_enabled(obs_source_t *source, bool enable) +{ + if (!obs_ptr_valid(source, "obs_source_set_monitor_enabled")) + return; + + struct calldata data; + uint8_t stack[128]; + + calldata_init_fixed(&data, stack, sizeof(stack)); + calldata_set_ptr(&data, "source", source); + calldata_set_bool(&data, "enabled", enable); + + signal_handler_signal(source->context.signals, "monitor_enable", &data); + + set_mute_and_monitor(source, obs_source_muted(source), enable); +} + +bool obs_source_monitor_enabled(const obs_source_t *source) +{ + if (!obs_ptr_valid(source, "obs_source_monitor_enabled")) + return false; + + enum obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); + + return monitoring_type != OBS_MONITORING_TYPE_NONE; +} diff --git a/libobs/obs.h b/libobs/obs.h index ca887bca8e31c3..79b566874877ac 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -1357,6 +1357,8 @@ enum obs_monitoring_type { EXPORT void obs_source_set_monitoring_type(obs_source_t *source, enum obs_monitoring_type type); EXPORT enum obs_monitoring_type obs_source_get_monitoring_type(const obs_source_t *source); +EXPORT void obs_source_set_monitor_enabled(obs_source_t *source, bool enable); +EXPORT bool obs_source_monitor_enabled(const obs_source_t *source); /** Gets private front-end settings data. This data is saved/loaded * automatically. Returns an incremented reference. */