Compare commits

...

7 Commits

Author SHA1 Message Date
WarmUpTill
465f55d3f3 Fix crash on shutdown 2026-08-24 20:38:53 +02:00
WarmUpTill
9758c14c33 Add mono option 2026-08-22 12:33:47 +02:00
WarmUpTill
e76df589e2 Fix cmake configure errors during download retry
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2026-08-14 22:45:29 +02:00
WarmUpTill
1390565fc6 Disable additional warnings for whisper.cpp 2026-08-14 22:45:29 +02:00
WarmUpTill
d76a37c785 Add AdvancedSceneSwitcherVersion vendor request
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
Returns version and commit information
2026-08-12 20:55:34 +02:00
WarmUpTill
ce8a35adea CI: Include Ubuntu 26 artifacts in release draft 2026-08-12 20:55:34 +02:00
Rygtx
9030ba1adf Update locale (zh-CN.ini)
Some checks failed
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2026-08-10 21:27:25 +02:00
9 changed files with 2397 additions and 1559 deletions

View File

@@ -249,6 +249,14 @@ jobs:
name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-sources-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-*-source.*
- name: Rename artifacts for Ubuntu 24 🏷️
run: |
: Rename artifacts for Ubuntu 24 🏷️
for f in ${{ github.workspace }}/release/*-x86_64-linux-gnu.*; do
[ -e "${f}" ] || continue
mv "${f}" "${f/x86_64-linux-gnu/x86_64-ubuntu24.04-linux-gnu}"
done
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v7
with:
@@ -320,6 +328,14 @@ jobs:
target: x86_64
config: ${{ needs.check-event.outputs.config }}
- name: Rename artifacts for Ubuntu 26 🏷️
run: |
: Rename artifacts for Ubuntu 26 🏷️
for f in ${{ github.workspace }}/release/*-x86_64-linux-gnu.*; do
[ -e "${f}" ] || continue
mv "${f}" "${f/x86_64-linux-gnu/x86_64-ubuntu26.04-linux-gnu}"
done
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v7
with:

View File

@@ -78,6 +78,7 @@ jobs:
'windows-x64;zip|exe'
'macos-universal;tar.xz|pkg'
'ubuntu-24.04-x86_64;tar.xz|deb|ddeb'
'ubuntu-26.04-x86_64;tar.xz|deb|ddeb'
'sources;tar.xz'
)

View File

@@ -191,14 +191,21 @@ function(_check_dependencies)
foreach(i RANGE 1 ${MAX_DOWNLOAD_RETRIES})
message(STATUS "Attempt ${i}/${MAX_DOWNLOAD_RETRIES} for ${url}")
file(
DOWNLOAD "${url}" "${dependencies_dir}/${file}"
STATUS download_status
EXPECTED_HASH SHA256=${hash})
file(DOWNLOAD "${url}" "${dependencies_dir}/${file}"
STATUS download_status)
list(GET download_status 0 error_code)
list(GET download_status 1 error_message)
if(error_code EQUAL 0)
file(SHA256 "${dependencies_dir}/${file}" actual_hash)
if(NOT actual_hash STREQUAL hash)
set(error_code 1)
set(error_message
"hash mismatch (expected ${hash}, got ${actual_hash})")
endif()
endif()
if(error_code EQUAL 0)
message(STATUS "Downloading ${url} - success on attempt ${i}")
set(download_success TRUE)

View File

@@ -973,6 +973,7 @@ AdvSceneSwitcher.action.playAudio.monitorUnavailable="Audio monitoring not avail
AdvSceneSwitcher.action.playAudio.tracks="Tracks"
AdvSceneSwitcher.action.playAudio.layout.startOffset="{{useStartOffset}}Start at{{startOffset}}"
AdvSceneSwitcher.action.playAudio.layout.playbackDuration="{{useDuration}}Play for{{playbackDuration}}(0 = until end)"
AdvSceneSwitcher.action.playAudio.mono="Mono"
AdvSceneSwitcher.action.recording="Recording"
AdvSceneSwitcher.action.recording.type.stop="Stop recording"
AdvSceneSwitcher.action.recording.type.start="Start recording"

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
#include "websocket-api.hpp"
#include "obs-websocket-api.h"
#include "plugin-state-helpers.hpp"
#include "version.h"
// Must be after "obs-websocket-api.h" to avoid logging function conflict
#include "log-helper.hpp"
@@ -15,6 +16,7 @@ static constexpr char VendorName[] = "AdvancedSceneSwitcher";
static constexpr char VendorRequestStart[] = "AdvancedSceneSwitcherStart";
static constexpr char VendorRequestStop[] = "AdvancedSceneSwitcherStop";
static constexpr char VendorRequestStatus[] = "IsAdvancedSceneSwitcherRunning";
static constexpr char VendorRequestVersion[] = "AdvancedSceneSwitcherVersion";
static obs_websocket_vendor vendor;
static void registerWebsocketVendor();
@@ -82,6 +84,12 @@ static void registerWebsocketVendor()
obs_data_set_bool(response, "isRunning",
PluginIsRunning());
});
registerWebsocketVendorRequest(
VendorRequestVersion,
[](obs_data_t *, obs_data_t *response, void *) {
obs_data_set_string(response, "version", g_GIT_TAG);
obs_data_set_string(response, "commit", g_GIT_SHA1);
});
}
const char *GetWebsocketVendorName()

View File

@@ -2,18 +2,64 @@
#include "audio-helpers.hpp"
#include "layout-helpers.hpp"
#include "macro-helpers.hpp"
#include "plugin-state-helpers.hpp"
#include "sync-helpers.hpp"
#include <obs-frontend-api.h>
#include <chrono>
#include <mutex>
#include <set>
#include <QFileInfo>
#include <QLabel>
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;
// 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";
bool MacroActionPlayAudio::_registered = MacroActionFactory::Register(
@@ -23,6 +69,7 @@ bool MacroActionPlayAudio::_registered = MacroActionFactory::Register(
static void deactivatePlayback(obs_source_t *source, bool wantsOutput)
{
obs_source_media_stop(source);
if (wantsOutput) {
obs_set_output_source(kPlaybackOutputChannel, nullptr);
} else {
@@ -38,8 +85,7 @@ static void waitForPlaybackToEnd(Macro *macro, obs_source_t *source,
std::unique_lock<std::mutex> lock(*GetMutex());
SetMacroAbortWait(false);
// The media source needs time to open and decode before reaching
// PLAYING state. Poll until it starts (or the macro is stopped).
// Poll until the source starts playing or the macro is stopped.
while (!MacroWaitShouldAbort() && !MacroIsStopped(macro)) {
if (obs_source_media_get_state(source) ==
OBS_MEDIA_STATE_PLAYING) {
@@ -48,9 +94,8 @@ static void waitForPlaybackToEnd(Macro *macro, obs_source_t *source,
GetMacroWaitCV().wait_for(lock, 10ms);
}
// Now wait for playback to end. Require two consecutive non-playing
// samples to avoid false positives on brief state transitions.
// If maxMs > 0, also stop once that many milliseconds have elapsed.
// Wait for playback to end. Two consecutive non-playing samples are
// required to avoid false positives on brief state transitions.
const auto playbackStart = std::chrono::steady_clock::now();
static const int kStopThreshold = 2;
int stoppedCount = 0;
@@ -95,7 +140,6 @@ bool MacroActionPlayAudio::PerformAction()
obs_data_set_string(settings, "local_file", path.c_str());
obs_data_set_bool(settings, "is_local_file", true);
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, "close_when_inactive", true);
@@ -113,10 +157,11 @@ bool MacroActionPlayAudio::PerformAction()
DecibelToPercent(static_cast<float>(_volumeDB.GetValue()));
obs_source_set_volume(source, vol);
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
// there is no point routing through the output channel if the mixer
// mask would silence every track.
// Fall back to monitor-only if all output tracks are deselected.
const bool wantsOutput =
(_monitorType != OBS_MONITORING_TYPE_MONITOR_ONLY) &&
(_audioMixers != 0);
@@ -128,10 +173,8 @@ bool MacroActionPlayAudio::PerformAction()
if (wantsOutput) {
obs_source_set_audio_mixers(source, _audioMixers);
// Route through a private scene so we can position the scene
// item far off-screen. This keeps the item "visible" (so audio
// is still mixed) while ensuring its video never intersects the
// output frame.
// Route through a private scene positioned off-screen so the
// audio mixes into the output without video appearing on screen.
OBSSceneAutoRelease audioScene =
obs_scene_create_private("advss_audio_scene");
obs_sceneitem_t *item = obs_scene_add(audioScene, source);
@@ -141,8 +184,7 @@ bool MacroActionPlayAudio::PerformAction()
}
obs_set_output_source(kPlaybackOutputChannel,
obs_scene_get_source(audioScene));
// audioScene released here; the output channel holds the
// remaining reference and keeps the scene alive.
// audioScene is released here; the output channel keeps the scene alive.
} else {
obs_source_set_audio_mixers(source, 0);
obs_source_inc_active(source);
@@ -167,15 +209,15 @@ bool MacroActionPlayAudio::PerformAction()
return true;
}
// Keep the source alive in a background thread that cleans up
// once playback finishes. Grab an extra strong reference so the
// source survives beyond this stack frame.
// Keep the source alive until playback ends via a background thread.
auto rawSource = obs_source_get_ref(source);
registerActiveSource(rawSource);
auto macro = GetMacro();
std::thread cleanupThread([rawSource, wantsOutput, macro, maxMs]() {
waitForPlaybackToEnd(macro, rawSource, maxMs);
deactivatePlayback(rawSource, wantsOutput);
unregisterActiveSource(rawSource);
obs_source_release(rawSource);
});
AddMacroHelperThread(macro, std::move(cleanupThread));
@@ -204,6 +246,7 @@ bool MacroActionPlayAudio::Save(obs_data_t *obj) const
obs_data_set_bool(obj, "useDuration", _useDuration);
_playbackDuration.Save(obj, "playbackDuration");
obs_data_set_bool(obj, "waitForCompletion", _waitForCompletion);
obs_data_set_bool(obj, "mono", _mono);
return true;
}
@@ -221,6 +264,7 @@ bool MacroActionPlayAudio::Load(obs_data_t *obj)
_useDuration = obs_data_get_bool(obj, "useDuration");
_playbackDuration.Load(obj, "playbackDuration");
_waitForCompletion = obs_data_get_bool(obj, "waitForCompletion");
_mono = obs_data_get_bool(obj, "mono");
return true;
}
@@ -262,7 +306,9 @@ MacroActionPlayAudioEdit::MacroActionPlayAudioEdit(
_useDuration(new QCheckBox(this)),
_playbackDuration(new DurationSelection(this, true, 0.0)),
_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->setMaximum(0.0);
@@ -303,6 +349,8 @@ MacroActionPlayAudioEdit::MacroActionPlayAudioEdit(
SLOT(PlaybackDurationChanged(const Duration &)));
QWidget::connect(_waitForCompletion, SIGNAL(stateChanged(int)), this,
SLOT(WaitChanged(int)));
QWidget::connect(_mono, SIGNAL(stateChanged(int)), this,
SLOT(MonoChanged(int)));
auto tracksLayout = new QHBoxLayout;
tracksLayout->setContentsMargins(0, 0, 0, 0);
@@ -346,6 +394,7 @@ MacroActionPlayAudioEdit::MacroActionPlayAudioEdit(
mainLayout->addLayout(startOffsetLayout);
mainLayout->addLayout(playbackDurationLayout);
mainLayout->addWidget(_waitForCompletion);
mainLayout->addWidget(_mono);
setLayout(mainLayout);
_entryData = entryData;
@@ -375,6 +424,7 @@ void MacroActionPlayAudioEdit::UpdateEntryData()
_playbackDuration->SetDuration(_entryData->_playbackDuration);
_playbackDuration->setEnabled(_entryData->_useDuration);
_waitForCompletion->setChecked(_entryData->_waitForCompletion);
_mono->setChecked(_entryData->_mono);
}
void MacroActionPlayAudioEdit::FilePathChanged(const QString &path)
@@ -443,4 +493,10 @@ void MacroActionPlayAudioEdit::WaitChanged(int value)
_entryData->_waitForCompletion = value;
}
void MacroActionPlayAudioEdit::MonoChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_mono = value;
}
} // namespace advss

View File

@@ -37,6 +37,7 @@ public:
bool _useDuration = false;
Duration _playbackDuration;
bool _waitForCompletion = false;
bool _mono = false;
static bool _registered;
static const std::string id;
@@ -68,6 +69,7 @@ private slots:
void UseDurationChanged(int);
void PlaybackDurationChanged(const Duration &);
void WaitChanged(int value);
void MonoChanged(int value);
signals:
void HeaderInfoChanged(const QString &);
@@ -83,6 +85,7 @@ private:
QCheckBox *_useDuration;
DurationSelection *_playbackDuration;
QCheckBox *_waitForCompletion;
QCheckBox *_mono;
std::shared_ptr<MacroActionPlayAudio> _entryData;
bool _loading = true;

View File

@@ -61,9 +61,14 @@ function(_advss_whisper_suppress_werror dir)
endif()
target_compile_options(
${_target}
PRIVATE $<$<C_COMPILER_ID:GNU,Clang,AppleClang>:-Wno-error>
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wno-error>
$<$<C_COMPILER_ID:MSVC>:/WX-> $<$<CXX_COMPILER_ID:MSVC>:/WX->)
PRIVATE $<$<C_COMPILER_ID:GNU,Clang,AppleClang>:-Wno-error
-Wno-shorten-64-to-32
-Wno-ambiguous-macro>
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wno-error
-Wno-shorten-64-to-32
-Wno-ambiguous-macro>
$<$<C_COMPILER_ID:MSVC>:/WX->
$<$<CXX_COMPILER_ID:MSVC>:/WX->)
endforeach()
endfunction()