mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-10-01 13:59:31 -05:00
Compare commits
7 Commits
dock-windo
...
twitch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8f4e524ba | ||
|
|
b104f98d02 | ||
|
|
abc8c3708f | ||
|
|
3b63db2bde | ||
|
|
fdc806be40 | ||
|
|
01107ee858 | ||
|
|
be8bc4a63f |
@@ -383,8 +383,18 @@ target_precompile_headers(${LIB_NAME} PRIVATE
|
|||||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/pch.hpp")
|
"${CMAKE_CURRENT_SOURCE_DIR}/lib/pch.hpp")
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
${LIB_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib"
|
${LIB_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/obs-websocket/lib")
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk")
|
|
||||||
|
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk/exprtk.hpp")
|
||||||
|
target_include_directories(${LIB_NAME}
|
||||||
|
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/deps/exprtk")
|
||||||
|
target_compile_definitions(${LIB_NAME} PRIVATE EXPRTK_SUPPORT=1)
|
||||||
|
else()
|
||||||
|
message(
|
||||||
|
STATUS
|
||||||
|
"${PROJECT_NAME}: deps/exprtk not found - math expression support disabled"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(NOT nlohmann_json_DIR
|
if(NOT nlohmann_json_DIR
|
||||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/deps/json/CMakeLists.txt")
|
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/deps/json/CMakeLists.txt")
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ AdvSceneSwitcher.macroTab.pauseStateSaveBehavior.unpause="Unpaused"
|
|||||||
AdvSceneSwitcher.macroTab.currentRegisterDock="Register dock widget to control the pause state of selected macro or run it manually"
|
AdvSceneSwitcher.macroTab.currentRegisterDock="Register dock widget to control the pause state of selected macro or run it manually"
|
||||||
AdvSceneSwitcher.macroTab.currentIsStandaloneDock="Is standalone dock"
|
AdvSceneSwitcher.macroTab.currentIsStandaloneDock="Is standalone dock"
|
||||||
AdvSceneSwitcher.macroTab.currentDockWindowName="Add to macro dock with name:"
|
AdvSceneSwitcher.macroTab.currentDockWindowName="Add to macro dock with name:"
|
||||||
|
AdvSceneSwitcher.macroTab.currentDockWindowName.addNew="Add new dock..."
|
||||||
AdvSceneSwitcher.macroTab.currentDockAddRunButton="Add button to run the macro"
|
AdvSceneSwitcher.macroTab.currentDockAddRunButton="Add button to run the macro"
|
||||||
AdvSceneSwitcher.macroTab.currentDockAddPauseButton="Add button to pause or unpause the macro"
|
AdvSceneSwitcher.macroTab.currentDockAddPauseButton="Add button to pause or unpause the macro"
|
||||||
AdvSceneSwitcher.macroTab.currentDockAddStatusLabel="Add status label"
|
AdvSceneSwitcher.macroTab.currentDockAddStatusLabel="Add status label"
|
||||||
@@ -985,6 +986,7 @@ AdvSceneSwitcher.action.playAudio.monitorUnavailable="Audio monitoring not avail
|
|||||||
AdvSceneSwitcher.action.playAudio.tracks="Tracks"
|
AdvSceneSwitcher.action.playAudio.tracks="Tracks"
|
||||||
AdvSceneSwitcher.action.playAudio.layout.startOffset="{{useStartOffset}}Start at{{startOffset}}"
|
AdvSceneSwitcher.action.playAudio.layout.startOffset="{{useStartOffset}}Start at{{startOffset}}"
|
||||||
AdvSceneSwitcher.action.playAudio.layout.playbackDuration="{{useDuration}}Play for{{playbackDuration}}(0 = until end)"
|
AdvSceneSwitcher.action.playAudio.layout.playbackDuration="{{useDuration}}Play for{{playbackDuration}}(0 = until end)"
|
||||||
|
AdvSceneSwitcher.action.playAudio.mono="Mono"
|
||||||
AdvSceneSwitcher.action.recording="Recording"
|
AdvSceneSwitcher.action.recording="Recording"
|
||||||
AdvSceneSwitcher.action.recording.type.stop="Stop recording"
|
AdvSceneSwitcher.action.recording.type.stop="Stop recording"
|
||||||
AdvSceneSwitcher.action.recording.type.start="Start recording"
|
AdvSceneSwitcher.action.recording.type.start="Start recording"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "macro-settings.hpp"
|
#include "macro-settings.hpp"
|
||||||
#include "layout-helpers.hpp"
|
#include "layout-helpers.hpp"
|
||||||
|
#include "macro-helpers.hpp"
|
||||||
#include "macro.hpp"
|
#include "macro.hpp"
|
||||||
#include "obs-module-helper.hpp"
|
#include "obs-module-helper.hpp"
|
||||||
#include "plugin-state-helpers.hpp"
|
#include "plugin-state-helpers.hpp"
|
||||||
@@ -8,11 +9,48 @@
|
|||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
static GlobalMacroSettings macroSettings;
|
static GlobalMacroSettings macroSettings;
|
||||||
|
|
||||||
|
static constexpr int addNewDockWindowRole = Qt::UserRole + 1;
|
||||||
|
|
||||||
|
static QStringList GetExistingDockWindowNames()
|
||||||
|
{
|
||||||
|
std::set<std::string> names;
|
||||||
|
for (const auto ¯o : GetAllMacros()) {
|
||||||
|
if (!macro) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto &dockSettings = macro->GetDockSettings();
|
||||||
|
if (dockSettings.IsStandaloneDock()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
names.insert(dockSettings.DockWindowName());
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList result;
|
||||||
|
for (const auto &name : names) {
|
||||||
|
result << QString::fromStdString(name);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void populateDockWindowNameCombo(QComboBox *combo)
|
||||||
|
{
|
||||||
|
combo->clear();
|
||||||
|
auto names = GetExistingDockWindowNames();
|
||||||
|
combo->addItems(names);
|
||||||
|
if (!names.isEmpty()) {
|
||||||
|
combo->insertSeparator(combo->count());
|
||||||
|
}
|
||||||
|
combo->addItem(obs_module_text(
|
||||||
|
"AdvSceneSwitcher.macroTab.currentDockWindowName.addNew"));
|
||||||
|
combo->setItemData(combo->count() - 1, true, addNewDockWindowRole);
|
||||||
|
}
|
||||||
|
|
||||||
void GlobalMacroSettings::Save(obs_data_t *obj) const
|
void GlobalMacroSettings::Save(obs_data_t *obj) const
|
||||||
{
|
{
|
||||||
auto data = obs_data_create();
|
auto data = obs_data_create();
|
||||||
@@ -96,7 +134,7 @@ MacroSettingsDialog::MacroSettingsDialog(QWidget *parent,
|
|||||||
"AdvSceneSwitcher.macroTab.currentRegisterDock"))),
|
"AdvSceneSwitcher.macroTab.currentRegisterDock"))),
|
||||||
_currentMacroIsStandaloneDock(new QCheckBox(obs_module_text(
|
_currentMacroIsStandaloneDock(new QCheckBox(obs_module_text(
|
||||||
"AdvSceneSwitcher.macroTab.currentIsStandaloneDock"))),
|
"AdvSceneSwitcher.macroTab.currentIsStandaloneDock"))),
|
||||||
_currentMacroDockWindowName(new QLineEdit(this)),
|
_currentMacroDockWindowName(new QComboBox(this)),
|
||||||
_currentMacroDockAddRunButton(new QCheckBox(obs_module_text(
|
_currentMacroDockAddRunButton(new QCheckBox(obs_module_text(
|
||||||
"AdvSceneSwitcher.macroTab.currentDockAddRunButton"))),
|
"AdvSceneSwitcher.macroTab.currentDockAddRunButton"))),
|
||||||
_currentMacroDockAddPauseButton(new QCheckBox(obs_module_text(
|
_currentMacroDockAddPauseButton(new QCheckBox(obs_module_text(
|
||||||
@@ -143,6 +181,17 @@ MacroSettingsDialog::MacroSettingsDialog(QWidget *parent,
|
|||||||
"AdvSceneSwitcher.macroTab.pauseStateSaveBehavior.unpause"),
|
"AdvSceneSwitcher.macroTab.pauseStateSaveBehavior.unpause"),
|
||||||
static_cast<int>(Macro::PauseStateSaveBehavior::UNPAUSE));
|
static_cast<int>(Macro::PauseStateSaveBehavior::UNPAUSE));
|
||||||
|
|
||||||
|
_currentMacroDockWindowName->setEditable(true);
|
||||||
|
populateDockWindowNameCombo(_currentMacroDockWindowName);
|
||||||
|
connect(_currentMacroDockWindowName,
|
||||||
|
qOverload<int>(&QComboBox::activated), this, [this](int index) {
|
||||||
|
if (_currentMacroDockWindowName
|
||||||
|
->itemData(index, addNewDockWindowRole)
|
||||||
|
.toBool()) {
|
||||||
|
_currentMacroDockWindowName->setCurrentText("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
auto highlightOptions = new QGroupBox(
|
auto highlightOptions = new QGroupBox(
|
||||||
obs_module_text("AdvSceneSwitcher.macroTab.highlightSettings"));
|
obs_module_text("AdvSceneSwitcher.macroTab.highlightSettings"));
|
||||||
auto highlightLayout = new QVBoxLayout;
|
auto highlightLayout = new QVBoxLayout;
|
||||||
@@ -348,7 +397,7 @@ MacroSettingsDialog::MacroSettingsDialog(QWidget *parent,
|
|||||||
_currentMacroRegisterDock->setChecked(dockEnabled);
|
_currentMacroRegisterDock->setChecked(dockEnabled);
|
||||||
_currentMacroIsStandaloneDock->setChecked(
|
_currentMacroIsStandaloneDock->setChecked(
|
||||||
dockSettings.IsStandaloneDock());
|
dockSettings.IsStandaloneDock());
|
||||||
_currentMacroDockWindowName->setText(
|
_currentMacroDockWindowName->setCurrentText(
|
||||||
QString::fromStdString(dockSettings.DockWindowName()));
|
QString::fromStdString(dockSettings.DockWindowName()));
|
||||||
_currentMacroDockAddRunButton->setChecked(dockSettings.HasRunButton());
|
_currentMacroDockAddRunButton->setChecked(dockSettings.HasRunButton());
|
||||||
_currentMacroDockAddPauseButton->setChecked(
|
_currentMacroDockAddPauseButton->setChecked(
|
||||||
@@ -486,7 +535,7 @@ bool MacroSettingsDialog::AskForSettings(QWidget *parent,
|
|||||||
dockSettings.SetIsStandaloneDock(
|
dockSettings.SetIsStandaloneDock(
|
||||||
dialog._currentMacroIsStandaloneDock->isChecked());
|
dialog._currentMacroIsStandaloneDock->isChecked());
|
||||||
dockSettings.SetDockWindowName(
|
dockSettings.SetDockWindowName(
|
||||||
dialog._currentMacroDockWindowName->text().toStdString());
|
dialog._currentMacroDockWindowName->currentText().toStdString());
|
||||||
dockSettings.SetHasRunButton(
|
dockSettings.SetHasRunButton(
|
||||||
dialog._currentMacroDockAddRunButton->isChecked());
|
dialog._currentMacroDockAddRunButton->isChecked());
|
||||||
dockSettings.SetHasPauseButton(
|
dockSettings.SetHasPauseButton(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
#include <QComboBox>
|
||||||
#include <QGroupBox>
|
#include <QGroupBox>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QGridLayout>
|
#include <QGridLayout>
|
||||||
@@ -71,7 +72,7 @@ private:
|
|||||||
MacroInputSelection *_currentInputs;
|
MacroInputSelection *_currentInputs;
|
||||||
QCheckBox *_currentMacroRegisterDock;
|
QCheckBox *_currentMacroRegisterDock;
|
||||||
QCheckBox *_currentMacroIsStandaloneDock;
|
QCheckBox *_currentMacroIsStandaloneDock;
|
||||||
QLineEdit *_currentMacroDockWindowName;
|
QComboBox *_currentMacroDockWindowName;
|
||||||
QCheckBox *_currentMacroDockAddRunButton;
|
QCheckBox *_currentMacroDockAddRunButton;
|
||||||
QCheckBox *_currentMacroDockAddPauseButton;
|
QCheckBox *_currentMacroDockAddPauseButton;
|
||||||
QCheckBox *_currentMacroDockAddStatusLabel;
|
QCheckBox *_currentMacroDockAddStatusLabel;
|
||||||
|
|||||||
@@ -2,13 +2,16 @@
|
|||||||
#include "obs-module-helper.hpp"
|
#include "obs-module-helper.hpp"
|
||||||
|
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
#ifdef EXPRTK_SUPPORT
|
||||||
#include <exprtk.hpp>
|
#include <exprtk.hpp>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
std::variant<double, std::string> EvalMathExpression(const std::string &expr)
|
std::variant<double, std::string> EvalMathExpression(const std::string &expr)
|
||||||
{
|
{
|
||||||
|
#ifdef EXPRTK_SUPPORT
|
||||||
static auto randomFunc = []() {
|
static auto randomFunc = []() {
|
||||||
thread_local std::mt19937 gen(std::random_device{}());
|
thread_local std::mt19937 gen(std::random_device{}());
|
||||||
thread_local std::uniform_real_distribution<double> dis(0.0,
|
thread_local std::uniform_real_distribution<double> dis(0.0,
|
||||||
@@ -26,6 +29,7 @@ std::variant<double, std::string> EvalMathExpression(const std::string &expr)
|
|||||||
if (parser.compile(expr, expression)) {
|
if (parser.compile(expr, expression)) {
|
||||||
return expression.value();
|
return expression.value();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
return std::string(obs_module_text(
|
return std::string(obs_module_text(
|
||||||
"AdvSceneSwitcher.math.expressionFail")) +
|
"AdvSceneSwitcher.math.expressionFail")) +
|
||||||
" \"" + expr + "\"";
|
" \"" + expr + "\"";
|
||||||
|
|||||||
@@ -127,9 +127,10 @@ void RegisterWebsocketRequest(
|
|||||||
|
|
||||||
void SendWebsocketVendorEvent(const std::string &eventName, obs_data_t *data)
|
void SendWebsocketVendorEvent(const std::string &eventName, obs_data_t *data)
|
||||||
{
|
{
|
||||||
if (OBSIsShuttingDown()) {
|
if (OBSIsShuttingDown() || !obs_get_module("obs-websocket")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_websocket_vendor_emit_event(vendor, eventName.c_str(), data);
|
obs_websocket_vendor_emit_event(vendor, eventName.c_str(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,18 +2,64 @@
|
|||||||
#include "audio-helpers.hpp"
|
#include "audio-helpers.hpp"
|
||||||
#include "layout-helpers.hpp"
|
#include "layout-helpers.hpp"
|
||||||
#include "macro-helpers.hpp"
|
#include "macro-helpers.hpp"
|
||||||
|
#include "plugin-state-helpers.hpp"
|
||||||
#include "sync-helpers.hpp"
|
#include "sync-helpers.hpp"
|
||||||
|
|
||||||
|
#include <obs-frontend-api.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
|
||||||
namespace advss {
|
namespace advss {
|
||||||
|
|
||||||
// Use a high output channel index that is unlikely to be claimed by OBS or
|
|
||||||
// other plugins. OBS supports channels 0-63; channel 0 is the main scene.
|
|
||||||
static constexpr uint32_t kPlaybackOutputChannel = 63;
|
static constexpr uint32_t kPlaybackOutputChannel = 63;
|
||||||
|
|
||||||
|
// Tracks rawSource handles held by background cleanup threads so they can be
|
||||||
|
// stopped early when OBS begins shutdown, before audio is freed.
|
||||||
|
static std::mutex g_activeSourcesMutex;
|
||||||
|
static std::set<obs_source_t *> g_activeSources;
|
||||||
|
|
||||||
|
static void registerActiveSource(obs_source_t *source)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_activeSourcesMutex);
|
||||||
|
g_activeSources.insert(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unregisterActiveSource(obs_source_t *source)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_activeSourcesMutex);
|
||||||
|
g_activeSources.erase(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stopAllActiveSources()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_activeSourcesMutex);
|
||||||
|
for (auto *source : g_activeSources) {
|
||||||
|
obs_source_media_stop(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleObsEvent(enum obs_frontend_event event, void *)
|
||||||
|
{
|
||||||
|
if (event == OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN ||
|
||||||
|
event == OBS_FRONTEND_EVENT_EXIT) {
|
||||||
|
stopAllActiveSources();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setup()
|
||||||
|
{
|
||||||
|
AddPluginPostLoadStep([]() {
|
||||||
|
obs_frontend_add_event_callback(handleObsEvent, nullptr);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setupDone = setup();
|
||||||
|
|
||||||
const std::string MacroActionPlayAudio::id = "play_audio";
|
const std::string MacroActionPlayAudio::id = "play_audio";
|
||||||
|
|
||||||
bool MacroActionPlayAudio::_registered = MacroActionFactory::Register(
|
bool MacroActionPlayAudio::_registered = MacroActionFactory::Register(
|
||||||
@@ -23,6 +69,7 @@ bool MacroActionPlayAudio::_registered = MacroActionFactory::Register(
|
|||||||
|
|
||||||
static void deactivatePlayback(obs_source_t *source, bool wantsOutput)
|
static void deactivatePlayback(obs_source_t *source, bool wantsOutput)
|
||||||
{
|
{
|
||||||
|
obs_source_media_stop(source);
|
||||||
if (wantsOutput) {
|
if (wantsOutput) {
|
||||||
obs_set_output_source(kPlaybackOutputChannel, nullptr);
|
obs_set_output_source(kPlaybackOutputChannel, nullptr);
|
||||||
} else {
|
} else {
|
||||||
@@ -38,8 +85,7 @@ static void waitForPlaybackToEnd(Macro *macro, obs_source_t *source,
|
|||||||
std::unique_lock<std::mutex> lock(*GetMutex());
|
std::unique_lock<std::mutex> lock(*GetMutex());
|
||||||
SetMacroAbortWait(false);
|
SetMacroAbortWait(false);
|
||||||
|
|
||||||
// The media source needs time to open and decode before reaching
|
// Poll until the source starts playing or the macro is stopped.
|
||||||
// PLAYING state. Poll until it starts (or the macro is stopped).
|
|
||||||
while (!MacroWaitShouldAbort() && !MacroIsStopped(macro)) {
|
while (!MacroWaitShouldAbort() && !MacroIsStopped(macro)) {
|
||||||
if (obs_source_media_get_state(source) ==
|
if (obs_source_media_get_state(source) ==
|
||||||
OBS_MEDIA_STATE_PLAYING) {
|
OBS_MEDIA_STATE_PLAYING) {
|
||||||
@@ -48,9 +94,8 @@ static void waitForPlaybackToEnd(Macro *macro, obs_source_t *source,
|
|||||||
GetMacroWaitCV().wait_for(lock, 10ms);
|
GetMacroWaitCV().wait_for(lock, 10ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now wait for playback to end. Require two consecutive non-playing
|
// Wait for playback to end. Two consecutive non-playing samples are
|
||||||
// samples to avoid false positives on brief state transitions.
|
// required to avoid false positives on brief state transitions.
|
||||||
// If maxMs > 0, also stop once that many milliseconds have elapsed.
|
|
||||||
const auto playbackStart = std::chrono::steady_clock::now();
|
const auto playbackStart = std::chrono::steady_clock::now();
|
||||||
static const int kStopThreshold = 2;
|
static const int kStopThreshold = 2;
|
||||||
int stoppedCount = 0;
|
int stoppedCount = 0;
|
||||||
@@ -95,7 +140,6 @@ bool MacroActionPlayAudio::PerformAction()
|
|||||||
obs_data_set_string(settings, "local_file", path.c_str());
|
obs_data_set_string(settings, "local_file", path.c_str());
|
||||||
obs_data_set_bool(settings, "is_local_file", true);
|
obs_data_set_bool(settings, "is_local_file", true);
|
||||||
obs_data_set_bool(settings, "looping", false);
|
obs_data_set_bool(settings, "looping", false);
|
||||||
// Disable automatic restart on activate so we control start explicitly.
|
|
||||||
obs_data_set_bool(settings, "restart_on_activate", false);
|
obs_data_set_bool(settings, "restart_on_activate", false);
|
||||||
obs_data_set_bool(settings, "close_when_inactive", true);
|
obs_data_set_bool(settings, "close_when_inactive", true);
|
||||||
|
|
||||||
@@ -113,10 +157,11 @@ bool MacroActionPlayAudio::PerformAction()
|
|||||||
DecibelToPercent(static_cast<float>(_volumeDB.GetValue()));
|
DecibelToPercent(static_cast<float>(_volumeDB.GetValue()));
|
||||||
obs_source_set_volume(source, vol);
|
obs_source_set_volume(source, vol);
|
||||||
obs_source_set_monitoring_type(source, _monitorType);
|
obs_source_set_monitoring_type(source, _monitorType);
|
||||||
|
if (_mono) {
|
||||||
|
obs_source_set_flags(source, OBS_SOURCE_FLAG_FORCE_MONO);
|
||||||
|
}
|
||||||
|
|
||||||
// Fall back to monitor-only if all output tracks are deselected —
|
// Fall back to monitor-only if all output tracks are deselected.
|
||||||
// there is no point routing through the output channel if the mixer
|
|
||||||
// mask would silence every track.
|
|
||||||
const bool wantsOutput =
|
const bool wantsOutput =
|
||||||
(_monitorType != OBS_MONITORING_TYPE_MONITOR_ONLY) &&
|
(_monitorType != OBS_MONITORING_TYPE_MONITOR_ONLY) &&
|
||||||
(_audioMixers != 0);
|
(_audioMixers != 0);
|
||||||
@@ -128,10 +173,8 @@ bool MacroActionPlayAudio::PerformAction()
|
|||||||
|
|
||||||
if (wantsOutput) {
|
if (wantsOutput) {
|
||||||
obs_source_set_audio_mixers(source, _audioMixers);
|
obs_source_set_audio_mixers(source, _audioMixers);
|
||||||
// Route through a private scene so we can position the scene
|
// Route through a private scene positioned off-screen so the
|
||||||
// item far off-screen. This keeps the item "visible" (so audio
|
// audio mixes into the output without video appearing on screen.
|
||||||
// is still mixed) while ensuring its video never intersects the
|
|
||||||
// output frame.
|
|
||||||
OBSSceneAutoRelease audioScene =
|
OBSSceneAutoRelease audioScene =
|
||||||
obs_scene_create_private("advss_audio_scene");
|
obs_scene_create_private("advss_audio_scene");
|
||||||
obs_sceneitem_t *item = obs_scene_add(audioScene, source);
|
obs_sceneitem_t *item = obs_scene_add(audioScene, source);
|
||||||
@@ -141,8 +184,7 @@ bool MacroActionPlayAudio::PerformAction()
|
|||||||
}
|
}
|
||||||
obs_set_output_source(kPlaybackOutputChannel,
|
obs_set_output_source(kPlaybackOutputChannel,
|
||||||
obs_scene_get_source(audioScene));
|
obs_scene_get_source(audioScene));
|
||||||
// audioScene released here; the output channel holds the
|
// audioScene is released here; the output channel keeps the scene alive.
|
||||||
// remaining reference and keeps the scene alive.
|
|
||||||
} else {
|
} else {
|
||||||
obs_source_set_audio_mixers(source, 0);
|
obs_source_set_audio_mixers(source, 0);
|
||||||
obs_source_inc_active(source);
|
obs_source_inc_active(source);
|
||||||
@@ -167,15 +209,15 @@ bool MacroActionPlayAudio::PerformAction()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the source alive in a background thread that cleans up
|
// Keep the source alive until playback ends via a background thread.
|
||||||
// once playback finishes. Grab an extra strong reference so the
|
|
||||||
// source survives beyond this stack frame.
|
|
||||||
auto rawSource = obs_source_get_ref(source);
|
auto rawSource = obs_source_get_ref(source);
|
||||||
|
registerActiveSource(rawSource);
|
||||||
auto macro = GetMacro();
|
auto macro = GetMacro();
|
||||||
|
|
||||||
std::thread cleanupThread([rawSource, wantsOutput, macro, maxMs]() {
|
std::thread cleanupThread([rawSource, wantsOutput, macro, maxMs]() {
|
||||||
waitForPlaybackToEnd(macro, rawSource, maxMs);
|
waitForPlaybackToEnd(macro, rawSource, maxMs);
|
||||||
deactivatePlayback(rawSource, wantsOutput);
|
deactivatePlayback(rawSource, wantsOutput);
|
||||||
|
unregisterActiveSource(rawSource);
|
||||||
obs_source_release(rawSource);
|
obs_source_release(rawSource);
|
||||||
});
|
});
|
||||||
AddMacroHelperThread(macro, std::move(cleanupThread));
|
AddMacroHelperThread(macro, std::move(cleanupThread));
|
||||||
@@ -204,6 +246,7 @@ bool MacroActionPlayAudio::Save(obs_data_t *obj) const
|
|||||||
obs_data_set_bool(obj, "useDuration", _useDuration);
|
obs_data_set_bool(obj, "useDuration", _useDuration);
|
||||||
_playbackDuration.Save(obj, "playbackDuration");
|
_playbackDuration.Save(obj, "playbackDuration");
|
||||||
obs_data_set_bool(obj, "waitForCompletion", _waitForCompletion);
|
obs_data_set_bool(obj, "waitForCompletion", _waitForCompletion);
|
||||||
|
obs_data_set_bool(obj, "mono", _mono);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +264,7 @@ bool MacroActionPlayAudio::Load(obs_data_t *obj)
|
|||||||
_useDuration = obs_data_get_bool(obj, "useDuration");
|
_useDuration = obs_data_get_bool(obj, "useDuration");
|
||||||
_playbackDuration.Load(obj, "playbackDuration");
|
_playbackDuration.Load(obj, "playbackDuration");
|
||||||
_waitForCompletion = obs_data_get_bool(obj, "waitForCompletion");
|
_waitForCompletion = obs_data_get_bool(obj, "waitForCompletion");
|
||||||
|
_mono = obs_data_get_bool(obj, "mono");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +306,9 @@ MacroActionPlayAudioEdit::MacroActionPlayAudioEdit(
|
|||||||
_useDuration(new QCheckBox(this)),
|
_useDuration(new QCheckBox(this)),
|
||||||
_playbackDuration(new DurationSelection(this, true, 0.0)),
|
_playbackDuration(new DurationSelection(this, true, 0.0)),
|
||||||
_waitForCompletion(new QCheckBox(
|
_waitForCompletion(new QCheckBox(
|
||||||
obs_module_text("AdvSceneSwitcher.action.playAudio.wait")))
|
obs_module_text("AdvSceneSwitcher.action.playAudio.wait"))),
|
||||||
|
_mono(new QCheckBox(
|
||||||
|
obs_module_text("AdvSceneSwitcher.action.playAudio.mono")))
|
||||||
{
|
{
|
||||||
_volumeDB->setMinimum(-100.0);
|
_volumeDB->setMinimum(-100.0);
|
||||||
_volumeDB->setMaximum(0.0);
|
_volumeDB->setMaximum(0.0);
|
||||||
@@ -303,6 +349,8 @@ MacroActionPlayAudioEdit::MacroActionPlayAudioEdit(
|
|||||||
SLOT(PlaybackDurationChanged(const Duration &)));
|
SLOT(PlaybackDurationChanged(const Duration &)));
|
||||||
QWidget::connect(_waitForCompletion, SIGNAL(stateChanged(int)), this,
|
QWidget::connect(_waitForCompletion, SIGNAL(stateChanged(int)), this,
|
||||||
SLOT(WaitChanged(int)));
|
SLOT(WaitChanged(int)));
|
||||||
|
QWidget::connect(_mono, SIGNAL(stateChanged(int)), this,
|
||||||
|
SLOT(MonoChanged(int)));
|
||||||
|
|
||||||
auto tracksLayout = new QHBoxLayout;
|
auto tracksLayout = new QHBoxLayout;
|
||||||
tracksLayout->setContentsMargins(0, 0, 0, 0);
|
tracksLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
@@ -346,6 +394,7 @@ MacroActionPlayAudioEdit::MacroActionPlayAudioEdit(
|
|||||||
mainLayout->addLayout(startOffsetLayout);
|
mainLayout->addLayout(startOffsetLayout);
|
||||||
mainLayout->addLayout(playbackDurationLayout);
|
mainLayout->addLayout(playbackDurationLayout);
|
||||||
mainLayout->addWidget(_waitForCompletion);
|
mainLayout->addWidget(_waitForCompletion);
|
||||||
|
mainLayout->addWidget(_mono);
|
||||||
setLayout(mainLayout);
|
setLayout(mainLayout);
|
||||||
|
|
||||||
_entryData = entryData;
|
_entryData = entryData;
|
||||||
@@ -375,6 +424,7 @@ void MacroActionPlayAudioEdit::UpdateEntryData()
|
|||||||
_playbackDuration->SetDuration(_entryData->_playbackDuration);
|
_playbackDuration->SetDuration(_entryData->_playbackDuration);
|
||||||
_playbackDuration->setEnabled(_entryData->_useDuration);
|
_playbackDuration->setEnabled(_entryData->_useDuration);
|
||||||
_waitForCompletion->setChecked(_entryData->_waitForCompletion);
|
_waitForCompletion->setChecked(_entryData->_waitForCompletion);
|
||||||
|
_mono->setChecked(_entryData->_mono);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacroActionPlayAudioEdit::FilePathChanged(const QString &path)
|
void MacroActionPlayAudioEdit::FilePathChanged(const QString &path)
|
||||||
@@ -443,4 +493,10 @@ void MacroActionPlayAudioEdit::WaitChanged(int value)
|
|||||||
_entryData->_waitForCompletion = value;
|
_entryData->_waitForCompletion = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MacroActionPlayAudioEdit::MonoChanged(int value)
|
||||||
|
{
|
||||||
|
GUARD_LOADING_AND_LOCK();
|
||||||
|
_entryData->_mono = value;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace advss
|
} // namespace advss
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public:
|
|||||||
bool _useDuration = false;
|
bool _useDuration = false;
|
||||||
Duration _playbackDuration;
|
Duration _playbackDuration;
|
||||||
bool _waitForCompletion = false;
|
bool _waitForCompletion = false;
|
||||||
|
bool _mono = false;
|
||||||
|
|
||||||
static bool _registered;
|
static bool _registered;
|
||||||
static const std::string id;
|
static const std::string id;
|
||||||
@@ -68,6 +69,7 @@ private slots:
|
|||||||
void UseDurationChanged(int);
|
void UseDurationChanged(int);
|
||||||
void PlaybackDurationChanged(const Duration &);
|
void PlaybackDurationChanged(const Duration &);
|
||||||
void WaitChanged(int value);
|
void WaitChanged(int value);
|
||||||
|
void MonoChanged(int value);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void HeaderInfoChanged(const QString &);
|
void HeaderInfoChanged(const QString &);
|
||||||
@@ -83,6 +85,7 @@ private:
|
|||||||
QCheckBox *_useDuration;
|
QCheckBox *_useDuration;
|
||||||
DurationSelection *_playbackDuration;
|
DurationSelection *_playbackDuration;
|
||||||
QCheckBox *_waitForCompletion;
|
QCheckBox *_waitForCompletion;
|
||||||
|
QCheckBox *_mono;
|
||||||
|
|
||||||
std::shared_ptr<MacroActionPlayAudio> _entryData;
|
std::shared_ptr<MacroActionPlayAudio> _entryData;
|
||||||
bool _loading = true;
|
bool _loading = true;
|
||||||
|
|||||||
@@ -233,7 +233,12 @@ void TwitchToken::Load(obs_data_t *obj)
|
|||||||
{
|
{
|
||||||
Item::Load(obj);
|
Item::Load(obj);
|
||||||
_token = obs_data_get_string(obj, "token");
|
_token = obs_data_get_string(obj, "token");
|
||||||
_userID = obs_data_get_string(obj, "userID");
|
if (obs_data_has_user_value(obj, "userID") &&
|
||||||
|
*obs_data_get_string(obj, "userID") != '\0') {
|
||||||
|
_userID = obs_data_get_string(obj, "userID");
|
||||||
|
} else {
|
||||||
|
_userID = {};
|
||||||
|
}
|
||||||
obs_data_set_default_bool(obj, "validateEventSubTimestamps", true);
|
obs_data_set_default_bool(obj, "validateEventSubTimestamps", true);
|
||||||
_validateEventSubTimestamps =
|
_validateEventSubTimestamps =
|
||||||
obs_data_get_bool(obj, "validateEventSubTimestamps");
|
obs_data_get_bool(obj, "validateEventSubTimestamps");
|
||||||
@@ -255,7 +260,7 @@ void TwitchToken::Save(obs_data_t *obj) const
|
|||||||
{
|
{
|
||||||
Item::Save(obj);
|
Item::Save(obj);
|
||||||
obs_data_set_string(obj, "token", _token.c_str());
|
obs_data_set_string(obj, "token", _token.c_str());
|
||||||
if (_userID) {
|
if (_userID && !_userID->empty()) {
|
||||||
obs_data_set_string(obj, "userID", _userID->c_str());
|
obs_data_set_string(obj, "userID", _userID->c_str());
|
||||||
}
|
}
|
||||||
obs_data_set_bool(obj, "validateEventSubTimestamps",
|
obs_data_set_bool(obj, "validateEventSubTimestamps",
|
||||||
@@ -310,7 +315,7 @@ bool TwitchToken::AnyOptionIsEnabled(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchToken::SetToken(const std::string &value)
|
bool TwitchToken::SetToken(const std::string &value)
|
||||||
{
|
{
|
||||||
_token = value;
|
_token = value;
|
||||||
auto res =
|
auto res =
|
||||||
@@ -318,21 +323,34 @@ void TwitchToken::SetToken(const std::string &value)
|
|||||||
if (res.status != 200) {
|
if (res.status != 200) {
|
||||||
blog(LOG_WARNING, "failed to get Twitch user id from token!");
|
blog(LOG_WARNING, "failed to get Twitch user id from token!");
|
||||||
_userID = {};
|
_userID = {};
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool gotUserID = false;
|
||||||
OBSDataArrayAutoRelease array = obs_data_get_array(res.data, "data");
|
OBSDataArrayAutoRelease array = obs_data_get_array(res.data, "data");
|
||||||
size_t count = obs_data_array_count(array);
|
size_t count = obs_data_array_count(array);
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
OBSDataAutoRelease arrayObj = obs_data_array_item(array, i);
|
OBSDataAutoRelease arrayObj = obs_data_array_item(array, i);
|
||||||
_userID = obs_data_get_string(arrayObj, "id");
|
std::string id = obs_data_get_string(arrayObj, "id");
|
||||||
|
if (id.empty()) {
|
||||||
|
blog(LOG_WARNING,
|
||||||
|
"Twitch /helix/users response did not contain a user id!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_userID = id;
|
||||||
_name = obs_data_get_string(arrayObj, "display_name");
|
_name = obs_data_get_string(arrayObj, "display_name");
|
||||||
|
gotUserID = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gotUserID) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger resubscribes with new token
|
// Trigger resubscribes with new token
|
||||||
if (_eventSub) {
|
if (_eventSub) {
|
||||||
_eventSub->ClearActiveSubscriptions();
|
_eventSub->ClearActiveSubscriptions();
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::string> TwitchToken::GetToken() const
|
std::optional<std::string> TwitchToken::GetToken() const
|
||||||
@@ -388,7 +406,7 @@ bool TwitchToken::IsValid(bool forceUpdate) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_userID && _userID != id) {
|
if (_userID && !_userID->empty() && _userID != id) {
|
||||||
blog(LOG_INFO,
|
blog(LOG_INFO,
|
||||||
"Twitch token %s does not match expected user (got %s, expected %s)!",
|
"Twitch token %s does not match expected user (got %s, expected %s)!",
|
||||||
_name.c_str(), id, _userID->c_str());
|
_name.c_str(), id, _userID->c_str());
|
||||||
@@ -396,6 +414,10 @@ bool TwitchToken::IsValid(bool forceUpdate) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_userID || _userID->empty()) {
|
||||||
|
_userID = id;
|
||||||
|
}
|
||||||
|
|
||||||
_lastValidityCheckResult = true;
|
_lastValidityCheckResult = true;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -819,9 +841,9 @@ void TwitchTokenSettingsDialog::GotToken(const std::optional<QString> &value)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentToken.SetToken(value.value().toStdString());
|
bool success = _currentToken.SetToken(value.value().toStdString());
|
||||||
auto name = QString::fromStdString(_currentToken._name);
|
auto name = QString::fromStdString(_currentToken._name);
|
||||||
if (name.isEmpty()) {
|
if (!success || name.isEmpty()) {
|
||||||
_tokenStatus->setText(obs_module_text(
|
_tokenStatus->setText(obs_module_text(
|
||||||
"AdvSceneSwitcher.twitchToken.request.fail"));
|
"AdvSceneSwitcher.twitchToken.request.fail"));
|
||||||
_name->setText("");
|
_name->setText("");
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public:
|
|||||||
bool OptionIsActive(const TokenOption &option) const;
|
bool OptionIsActive(const TokenOption &option) const;
|
||||||
bool OptionIsEnabled(const TokenOption &option) const;
|
bool OptionIsEnabled(const TokenOption &option) const;
|
||||||
bool AnyOptionIsEnabled(const std::vector<TokenOption> &options) const;
|
bool AnyOptionIsEnabled(const std::vector<TokenOption> &options) const;
|
||||||
void SetToken(const std::string &);
|
bool SetToken(const std::string &);
|
||||||
bool IsEmpty() const { return _token.empty(); }
|
bool IsEmpty() const { return _token.empty(); }
|
||||||
std::optional<std::string> GetToken() const;
|
std::optional<std::string> GetToken() const;
|
||||||
std::optional<std::string> GetUserID() const { return _userID; }
|
std::optional<std::string> GetUserID() const { return _userID; }
|
||||||
@@ -65,7 +65,7 @@ private:
|
|||||||
mutable std::string _lastValidityCheckValue;
|
mutable std::string _lastValidityCheckValue;
|
||||||
mutable bool _lastValidityCheckResult = false;
|
mutable bool _lastValidityCheckResult = false;
|
||||||
mutable std::chrono::system_clock::time_point _lastValidityCheckTime;
|
mutable std::chrono::system_clock::time_point _lastValidityCheckTime;
|
||||||
std::optional<std::string> _userID;
|
mutable std::optional<std::string> _userID;
|
||||||
std::set<TokenOption> _tokenOptions = TokenOption::GetAllTokenOptions();
|
std::set<TokenOption> _tokenOptions = TokenOption::GetAllTokenOptions();
|
||||||
std::shared_ptr<EventSub> _eventSub;
|
std::shared_ptr<EventSub> _eventSub;
|
||||||
bool _validateEventSubTimestamps = false;
|
bool _validateEventSubTimestamps = false;
|
||||||
|
|||||||
@@ -85,8 +85,11 @@ target_sources(
|
|||||||
${PROJECT_NAME}
|
${PROJECT_NAME}
|
||||||
PRIVATE test-math.cpp ${ADVSS_SOURCE_DIR}/lib/utils/math-helpers.cpp
|
PRIVATE test-math.cpp ${ADVSS_SOURCE_DIR}/lib/utils/math-helpers.cpp
|
||||||
${ADVSS_SOURCE_DIR}/lib/utils/obs-module-helper.cpp)
|
${ADVSS_SOURCE_DIR}/lib/utils/obs-module-helper.cpp)
|
||||||
target_include_directories(${PROJECT_NAME}
|
if(EXISTS "${ADVSS_SOURCE_DIR}/deps/exprtk/exprtk.hpp")
|
||||||
PRIVATE ${ADVSS_SOURCE_DIR}/deps/exprtk)
|
target_include_directories(${PROJECT_NAME}
|
||||||
|
PRIVATE ${ADVSS_SOURCE_DIR}/deps/exprtk)
|
||||||
|
target_compile_definitions(${PROJECT_NAME} PRIVATE EXPRTK_SUPPORT=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
target_compile_options(${PROJECT_NAME} PUBLIC /MP /d2FH4- /wd4267 /bigobj)
|
target_compile_options(${PROJECT_NAME} PUBLIC /MP /d2FH4- /wd4267 /bigobj)
|
||||||
|
|||||||
Reference in New Issue
Block a user