Compare commits

...

15 Commits
1.9 ... 1.9.1

Author SHA1 Message Date
WarmUpTill
33667a1f23 Fix crash on advancing to unset scene for extended sequence (#130) 2021-02-24 18:53:52 +01:00
WarmUpTill
7947ad2608 Add option to extend scene sequences (#121)
Extending a scene sequence allows for more complex switching setups, but should also simplify some setups.
Sequences like "A -> B -> A -> C", which previously were only possible by creating a copy of A, can now be specified in a single entry.
To extend a sequence either select the sequence you want to modify and click the extend sequence button or simply double click the entry.
2021-02-24 18:19:23 +01:00
WarmUpTill
920c188213 Add scene trigger for starting and stopping the scene switcher (#129) 2021-02-23 21:00:25 +01:00
WarmUpTill
28fbff1adc Allow specifying transition when reading scenes from file. (#128)
The transition can be specified on the second line of the file.
For example:
Scene 1
Slide
2021-02-21 20:17:32 +01:00
WarmUpTill
326617aad7 Fix checkSwitchInfoFromFile ignoring file switch puase (#127) 2021-02-21 20:16:51 +01:00
WarmUpTill
499ba7dd21 Update the UI's status element if the status changed externally (#126) 2021-02-20 18:58:07 +01:00
WarmUpTill
f4902933ab Fix getCurrentScene() returning garbage if scene group has type random (#125) 2021-02-20 18:08:47 +01:00
WarmUpTill
9aa0b31bff Add scene trigger to unmute source (#124) 2021-02-20 18:08:35 +01:00
WarmUpTill
6d5086b971 Add option to select 'current transition' (#123)
* move addSelectionEntry to AdvSceneSwitcher class and allow select entry to be 'selectable'

Can be useful for implementing 'current transition' as an option or just allowing to select 'None' for screen region ignore

* Add option to select 'current transition'
2021-02-19 19:03:38 +01:00
WarmUpTill
f32f47db4b Install qtbase5-private-dev on Linux (#122) 2021-02-13 19:28:24 +01:00
WarmUpTill
74c0cd1736 Adjust sleep log message and comment for sceneChangedDuringWait() (#120) 2021-02-13 19:14:28 +01:00
WarmUpTill
a74a952b1d Fix log messages containing two '[adv-ss]' tags (#118) 2021-02-07 18:41:47 +01:00
WarmUpTill
5bbfef81e0 Improve scene trigger handling (#117) 2021-02-06 18:23:57 +01:00
WarmUpTill
c31d1cd975 Delay setting of default scene transitions (#116) 2021-02-06 18:23:41 +01:00
WarmUpTill
81d91a9139 resovle name conflict (#115) 2021-02-04 07:24:52 +01:00
23 changed files with 876 additions and 245 deletions

View File

@@ -161,6 +161,7 @@ jobs:
pkg-config \
python3-dev \
qtbase5-dev \
qtbase5-private-dev \
libqt5svg5-dev \
swig \
libxss-dev

View File

@@ -7,6 +7,13 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/version.cpp" @ONLY)
if(WIN32)
get_filename_component(ISS_PLUGIN_FILES_DIR "${CMAKE_BINARY_DIR}\\..\\package" ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_PLUGIN_FILES_DIR}" ISS_PLUGIN_FILES_DIR)
get_filename_component(ISS_MSVC_REDIST_HELPER_DIR "${CMAKE_PACKAGE_PREFIX}\\.." ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_MSVC_REDIST_HELPER_DIR}" ISS_MSVC_REDIST_HELPER_DIR)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/CI/windows/setup.iss.in" "${CMAKE_CURRENT_BINARY_DIR}/CI/windows/setup.iss" @ONLY)
endif()
if(WIN32)
get_filename_component(ISS_PLUGIN_FILES_DIR "${CMAKE_BINARY_DIR}\\..\\package" ABSOLUTE)

View File

@@ -232,7 +232,10 @@ AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="Streamen stop
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="Streamen starten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="Replay Buffer starten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="Replay Buffer stoppen"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="Quelle stumm schalten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="Audio Quelle stumm schalten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="Audio Quelle aktiv schalten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="Szenenwechsler starten"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="Szenenwechsler stoppen"
AdvSceneSwitcher.sceneTriggerTab.entry="Wenn {{scenes}} {{triggers}} {{actions}} {{audioSources}} nach {{duration}} "
AdvSceneSwitcher.sceneTriggerTab.help="Dieser Tab ermöglicht es automatisiert Handlungen, wie etwa das Stoppen von einer Aufnahme oder des Streamens, bei einem Szenenwechsel auszuführen."
@@ -249,6 +252,7 @@ AdvSceneSwitcher.browse="Öffnen"
AdvSceneSwitcher.selectScene="--Szene auswählen--"
AdvSceneSwitcher.selectPreviousScene="Vorhergehende Szene"
AdvSceneSwitcher.currentTransition="Aktueller Szenenübergang"
AdvSceneSwitcher.selectTransition="--Szenenübergang auswählen--"
AdvSceneSwitcher.selectWindow="--Fenster auswählen--"
AdvSceneSwitcher.selectAudioSource="--Audio Quelle auswählen--"

View File

@@ -186,6 +186,8 @@ AdvSceneSwitcher.sceneSequenceTab.fileType="Text files (*.txt)"
AdvSceneSwitcher.sceneSequenceTab.interruptible="interruptible"
AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Other switching methods are allowed to interrupt this scene sequence"
AdvSceneSwitcher.sceneSequenceTab.entry="When {{startScenes}} is active switch to {{scenes}} after {{delay}} {{delayUnits}} using {{transitions}} {{interruptible}}"
AdvSceneSwitcher.sceneSequenceTab.extendEdit="Extend Sequence"
AdvSceneSwitcher.sceneSequenceTab.extendEntry="After {{delay}} {{delayUnits}} switch to {{scenes}} using {{transitions}}"
AdvSceneSwitcher.sceneSequenceTab.help="This tab will allow you to automatically switch to a different scene if a scene was active for a configured period of time.\nFor example, you could automatically cycle back and forth between two scenes automatically.\n\nClick on the highlighted plus symbol to continue."
; Audio Tab
@@ -233,6 +235,9 @@ AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="start stream
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="start replay buffer"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="stop replay buffer"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="mute source"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="unmute source"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="start the scene switcher"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="stop the scene switcher"
AdvSceneSwitcher.sceneTriggerTab.entry="When {{scenes}} {{triggers}} {{actions}} {{audioSources}} after {{duration}}"
AdvSceneSwitcher.sceneTriggerTab.help="This tab allows you to trigger actions on scene changes, like stopping recording or streaming."
@@ -248,6 +253,7 @@ AdvSceneSwitcher.browse="Browse"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.currentTransition="Current Transition"
AdvSceneSwitcher.selectTransition="--select transition--"
AdvSceneSwitcher.selectWindow="--select window--"
AdvSceneSwitcher.selectAudioSource="--select audio source--"

View File

@@ -2824,6 +2824,20 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sequenceEdit">
<property name="text">
<string>AdvSceneSwitcher.sceneSequenceTab.extendEdit</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_8">
<property name="orientation">

View File

@@ -92,8 +92,9 @@ bool AdvSceneSwitcher::DisplayMessage(QString msg, bool question)
return false;
}
void addSelectionEntry(QComboBox *sel, const char *description,
const char *tooltip = "")
void AdvSceneSwitcher::addSelectionEntry(QComboBox *sel,
const char *description,
bool selectable, const char *tooltip)
{
sel->addItem(description);
@@ -106,14 +107,17 @@ void addSelectionEntry(QComboBox *sel, const char *description,
QModelIndex firstIndex =
model->index(0, sel->modelColumn(), sel->rootModelIndex());
QStandardItem *firstItem = model->itemFromIndex(firstIndex);
firstItem->setSelectable(false);
firstItem->setEnabled(false);
if (!selectable) {
firstItem->setSelectable(false);
firstItem->setEnabled(false);
}
}
void AdvSceneSwitcher::populateSceneSelection(QComboBox *sel, bool addPrevious,
bool addSceneGroup,
bool addSelect,
std::string selectText)
std::string selectText,
bool selectable)
{
sel->clear();
@@ -122,10 +126,11 @@ void AdvSceneSwitcher::populateSceneSelection(QComboBox *sel, bool addPrevious,
addSelectionEntry(
sel,
obs_module_text("AdvSceneSwitcher.selectScene"),
selectable,
obs_module_text(
"AdvSceneSwitcher.invaildEntriesWillNotBeSaved"));
} else {
addSelectionEntry(sel, selectText.c_str());
addSelectionEntry(sel, selectText.c_str(), selectable);
}
}
@@ -150,12 +155,20 @@ void AdvSceneSwitcher::populateSceneSelection(QComboBox *sel, bool addPrevious,
}
void AdvSceneSwitcher::populateTransitionSelection(QComboBox *sel,
bool addSelect)
bool addCurrent,
bool addSelect,
bool selectable)
{
if (addSelect) {
addSelectionEntry(
sel,
obs_module_text("AdvSceneSwitcher.selectTransition"));
obs_module_text("AdvSceneSwitcher.selectTransition"),
selectable);
}
if (addCurrent) {
sel->addItem(
obs_module_text("AdvSceneSwitcher.currentTransition"));
}
obs_frontend_source_list *transitions = new obs_frontend_source_list();
@@ -197,6 +210,7 @@ void AdvSceneSwitcher::populateAudioSelection(QComboBox *sel, bool addSelect)
addSelectionEntry(
sel,
obs_module_text("AdvSceneSwitcher.selectAudioSource"),
false,
obs_module_text(
"AdvSceneSwitcher.invaildEntriesWillNotBeSaved"));
}
@@ -227,6 +241,7 @@ void AdvSceneSwitcher::populateMediaSelection(QComboBox *sel, bool addSelect)
addSelectionEntry(
sel,
obs_module_text("AdvSceneSwitcher.selectMediaSource"),
false,
obs_module_text(
"AdvSceneSwitcher.invaildEntriesWillNotBeSaved"));
}
@@ -428,9 +443,6 @@ void SwitcherData::Thread()
OBSWeakSource scene;
OBSWeakSource transition;
bool defTransitionMatch = false;
OBSWeakSource defTransition;
endTime = std::chrono::high_resolution_clock::now();
auto runTime =
@@ -449,7 +461,7 @@ void SwitcherData::Thread()
}
}
vblog(LOG_INFO, "sleep for %ld", duration.count());
vblog(LOG_INFO, "try to sleep for %ld", duration.count());
cv.wait_for(lock, duration);
startTime = std::chrono::high_resolution_clock::now();
@@ -457,63 +469,17 @@ void SwitcherData::Thread()
linger = 0;
switcher->Prune();
if (switcher->stop) {
break;
}
if (checkPause()) {
continue;
}
checkDefaultSceneTransitions(defTransitionMatch, defTransition);
for (int switchFuncName : functionNamesByPriority) {
switch (switchFuncName) {
case read_file_func:
checkSwitchInfoFromFile(match, scene,
transition);
checkFileContent(match, scene, transition);
break;
case idle_func:
checkIdleSwitch(match, scene, transition);
break;
case exe_func:
checkExeSwitch(match, scene, transition);
break;
case screen_region_func:
checkScreenRegionSwitch(match, scene,
transition);
break;
case window_title_func:
checkWindowTitleSwitch(match, scene,
transition);
break;
case round_trip_func:
checkSceneSequence(match, scene, transition,
linger);
break;
case media_func:
checkMediaSwitch(match, scene, transition);
break;
case time_func:
checkTimeSwitch(match, scene, transition);
break;
case audio_func:
checkAudioSwitch(match, scene, transition);
break;
}
if (switcher->stop) {
goto endLoop;
}
if (match) {
break;
}
match = checkForMatch(scene, transition, linger);
if (switcher->stop) {
break;
}
checkNoMatchSwitch(match, scene, transition, sleep);
checkSwitchCooldown(match);
if (linger) {
@@ -544,10 +510,6 @@ void SwitcherData::Thread()
// leading to a deadlock, so we have to unlock()
lock.unlock();
if (!match && defTransitionMatch) {
setCurrentDefTransition(defTransition);
}
if (match) {
switchScene(scene, transition,
tansitionOverrideOverride);
@@ -555,10 +517,64 @@ void SwitcherData::Thread()
writeSceneInfoToFile();
}
endLoop:
blog(LOG_INFO, "stopped");
}
bool SwitcherData::checkForMatch(OBSWeakSource &scene,
OBSWeakSource &transition, int &linger)
{
bool match = false;
if (uninterruptibleSceneSequenceActive) {
checkSceneSequence(match, scene, transition, linger);
if (match) {
return match;
}
}
for (int switchFuncName : functionNamesByPriority) {
switch (switchFuncName) {
case read_file_func:
checkSwitchInfoFromFile(match, scene, transition);
checkFileContent(match, scene, transition);
break;
case idle_func:
checkIdleSwitch(match, scene, transition);
break;
case exe_func:
checkExeSwitch(match, scene, transition);
break;
case screen_region_func:
checkScreenRegionSwitch(match, scene, transition);
break;
case window_title_func:
checkWindowTitleSwitch(match, scene, transition);
break;
case round_trip_func:
checkSceneSequence(match, scene, transition, linger);
break;
case media_func:
checkMediaSwitch(match, scene, transition);
break;
case time_func:
checkTimeSwitch(match, scene, transition);
break;
case audio_func:
checkAudioSwitch(match, scene, transition);
break;
}
if (switcher->stop) {
return false;
}
if (match) {
break;
}
}
return match;
}
void switchScene(OBSWeakSource &scene, OBSWeakSource &transition,
bool &transitionOverrideOverride)
{
@@ -643,7 +659,7 @@ extern "C" void FreeSceneSwitcher()
void handleSceneChange(SwitcherData *s)
{
// Stop waiting if scene was manually changed
// Stop waiting if scene was changed
if (s->sceneChangedDuringWait()) {
s->cv.notify_one();
}
@@ -658,12 +674,8 @@ void handleSceneChange(SwitcherData *s)
s->previousSceneHelper = ws;
}
switcher->checkTriggers();
}
void handleTransitionStop(SwitcherData *s)
{
s->checkedDefTransition = false;
s->checkTriggers();
s->checkDefaultSceneTransitions();
}
void setLiveTime(SwitcherData *s)
@@ -702,9 +714,6 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
handleSceneChange((SwitcherData *)switcher);
break;
case OBS_FRONTEND_EVENT_TRANSITION_STOPPED:
handleTransitionStop((SwitcherData *)switcher);
break;
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
setLiveTime((SwitcherData *)switcher);
checkAutoStartRecording((SwitcherData *)switcher);

View File

@@ -129,6 +129,7 @@ void AdvSceneSwitcher::SetStarted()
ui->pluginRunningText->setText(
obs_module_text("AdvSceneSwitcher.status.active"));
ui->pluginRunningText->disconnect(inactivePluse);
currentStatusActive = true;
}
void AdvSceneSwitcher::SetStopped()
@@ -139,6 +140,7 @@ void AdvSceneSwitcher::SetStopped()
obs_module_text("AdvSceneSwitcher.status.inactive"));
inactivePluse = PulseWidget(ui->pluginRunningText, QColor(Qt::red),
QColor(0, 0, 0, 0), "QLabel ");
currentStatusActive = false;
}
void AdvSceneSwitcher::on_toggleStartButton_clicked()
@@ -681,6 +683,21 @@ void populateAutoStartEventSelection(QComboBox *cb)
"AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming"));
}
void AdvSceneSwitcher::updateStatus()
{
if (switcher->th && switcher->th->isRunning()) {
if (currentStatusActive) {
return;
}
SetStarted();
} else {
if (!currentStatusActive) {
return;
}
SetStopped();
}
}
void AdvSceneSwitcher::setupGeneralTab()
{
populateSceneSelection(ui->noMatchSwitchScene, false);
@@ -780,4 +797,10 @@ void AdvSceneSwitcher::setupGeneralTab()
} else {
SetStopped();
}
// Updates the UI status element if the status changed externally
// (e.g. via hotkeys)
QTimer *statusTimer = new QTimer(this);
connect(statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
statusTimer->start(1000);
}

View File

@@ -7,9 +7,9 @@
#include "switcher-data-structs.hpp"
#define blog(level, msg, ...) blog(level, "[adv-ss] " msg, ##__VA_ARGS__)
#define vblog(level, msg, ...) \
if (verbose) { \
blog(level, "[adv-ss] " msg, ##__VA_ARGS__); \
#define vblog(level, msg, ...) \
if (verbose) { \
blog(level, msg, ##__VA_ARGS__); \
}
class QCloseEvent;
@@ -23,6 +23,7 @@ class AdvSceneSwitcher : public QDialog {
public:
std::unique_ptr<Ui_AdvSceneSwitcher> ui;
bool loading = true;
bool currentStatusActive = false;
AdvSceneSwitcher(QWidget *parent);
@@ -39,6 +40,7 @@ public:
int IgnoreIdleWindowsFindByData(const QString &window);
void UpdateNonMatchingScene(const QString &name);
void OpenSequenceExtendEdit(SequenceWidget *sw);
void SetEditSceneGroup(SceneGroup &sg);
void loadUI();
@@ -62,14 +64,20 @@ public:
static bool DisplayMessage(QString msg, bool question = false);
static void AskBackup(obs_data_t *obj);
static void addSelectionEntry(QComboBox *sel, const char *description,
bool selectable = false,
const char *tooltip = "");
static void populateSceneSelection(QComboBox *sel,
bool addPrevious = false,
bool addSceneGroup = false,
bool addSelect = true,
std::string selectText = "");
std::string selectText = "",
bool selectable = false);
static void populateTransitionSelection(QComboBox *sel,
bool addSelect = true);
bool addCurrent = true,
bool addSelect = true,
bool selectable = false);
static void populateWindowSelection(QComboBox *sel,
bool addSelect = true);
static void populateAudioSelection(QComboBox *sel,
@@ -129,10 +137,12 @@ public slots:
void on_sceneSequenceAdd_clicked();
void on_sceneSequenceRemove_clicked();
void on_sceneSequenceSave_clicked();
void on_sceneSequenceLoad_clicked();
void on_sceneSequenceUp_clicked();
void on_sceneSequenceDown_clicked();
void on_sceneSequenceSave_clicked();
void on_sceneSequenceLoad_clicked();
void on_sequenceEdit_clicked();
void on_sceneSequenceSwitches_itemDoubleClicked(QListWidgetItem *item);
void on_verboseLogging_stateChanged(int state);
void on_uiHintsDisable_stateChanged(int state);
@@ -213,6 +223,7 @@ public slots:
void on_threadPriority_currentTextChanged(const QString &text);
void updateScreenRegionCursorPos();
void updateStatus();
void on_close_clicked();

View File

@@ -37,7 +37,7 @@ struct SceneGroup {
int currentCount = -1;
std::chrono::high_resolution_clock::time_point lastAdvTime;
OBSWeakSource lastRandomScene = nullptr;
int lastRandomScene = -1;
inline SceneGroup(){};
inline SceneGroup(std::string name_) : name(name_){};

View File

@@ -23,6 +23,10 @@ enum class sceneTriggerAction {
STOP_REPLAY_BUFFER,
MUTE_SOURCE,
UNMUTE_SOURCE,
START_SWITCHER,
STOP_SWITCHER,
};
struct SceneTrigger : SceneSwitcherEntry {
@@ -36,7 +40,8 @@ struct SceneTrigger : SceneSwitcherEntry {
void save(obs_data_t *obj);
void load(obs_data_t *obj);
bool checkMatch(OBSWeakSource previousScene);
bool checkMatch(OBSWeakSource currentScene,
OBSWeakSource previousScene);
void performAction();
void logMatch();
};

View File

@@ -8,6 +8,7 @@ struct SceneSwitcherEntry {
OBSWeakSource scene = nullptr;
OBSWeakSource transition = nullptr;
bool usePreviousScene = false;
bool useCurrentTransition = false;
virtual const char *getType() = 0;
virtual bool initialized();
@@ -64,14 +65,15 @@ class SwitchWidget : public QWidget {
public:
SwitchWidget(QWidget *parent, SceneSwitcherEntry *s,
bool usePreviousScene = true, bool addSceneGroup = false);
bool usePreviousScene = true, bool addSceneGroup = false,
bool addCurrentTransition = true);
virtual SceneSwitcherEntry *getSwitchData();
virtual void setSwitchData(SceneSwitcherEntry *s);
void showSwitchData();
static void swapSwitchData(SwitchWidget *s1, SwitchWidget *s2);
private slots:
protected slots:
void SceneChanged(const QString &text);
void TransitionChanged(const QString &text);
void SceneGroupAdd(const QString &name);

View File

@@ -1,4 +1,6 @@
#pragma once
#include <QVBoxLayout>
#include "switch-generic.hpp"
constexpr auto round_trip_func = 1;
@@ -12,42 +14,77 @@ typedef enum {
struct SceneSequenceSwitch : SceneSwitcherEntry {
static bool pause;
SwitchTargetType startTargetType = SwitchTargetType::Scene;
OBSWeakSource startScene = nullptr;
double delay = 0;
int delayMultiplier = 1;
bool interruptible = false;
unsigned int matchCount = 0;
// nullptr marks start point and reaching end of extended sequence
SceneSequenceSwitch *activeSequence = nullptr;
std::unique_ptr<SceneSequenceSwitch> extendedSequence = nullptr;
const char *getType() { return "sequence"; }
bool initialized();
bool valid();
void save(obs_data_t *obj);
void load(obs_data_t *obj);
void save(obs_data_t *obj, bool saveExt = true);
void load(obs_data_t *obj, bool saveExt = true);
bool reduce();
SceneSequenceSwitch *extend();
bool checkMatch(OBSWeakSource currentScene, int &linger,
SceneSequenceSwitch *root = nullptr);
bool checkDurationMatchInterruptible();
void prepareUninterruptibleMatch(OBSWeakSource currentScene,
int &linger);
void advanceActiveSequence();
void logAdvanceSequence();
void logSequenceCanceled();
};
class SequenceWidget : public SwitchWidget {
Q_OBJECT
public:
SequenceWidget(QWidget *parent, SceneSequenceSwitch *s);
SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
bool extendSequence = false,
bool editExtendMode = false);
SceneSequenceSwitch *getSwitchData();
void setSwitchData(SceneSequenceSwitch *s);
static void swapSwitchData(SequenceWidget *s1, SequenceWidget *s2);
void UpdateDelay();
void UpdateExtendText();
void setExtendedSequenceStartScene();
private slots:
void SceneChanged(const QString &text);
void DelayChanged(double delay);
void DelayUnitsChanged(int idx);
void StartSceneChanged(const QString &text);
void InterruptibleChanged(int state);
void ExtendClicked();
void ReduceClicked();
private:
protected:
QDoubleSpinBox *delay;
QComboBox *delayUnits;
QComboBox *startScenes;
QCheckBox *interruptible;
QVBoxLayout *extendSequenceLayout;
QPushButton *extend;
QPushButton *reduce;
// I would prefer having a list of only widgets of type editExtendMode
// but I am not sure how to best implement that using a QListWidget.
//
// So use edit button to bring up edit widget and
// add a label to disaplay current extended sequence state.
QLabel *extendText;
SceneSequenceSwitch *switchData;
};

View File

@@ -20,6 +20,8 @@ struct DefaultSceneTransition : SceneSwitcherEntry {
static bool pause;
const char *getType() { return "def_transition"; }
bool checkMatch(OBSWeakSource currentScene);
void setTransition();
inline DefaultSceneTransition(){};
inline DefaultSceneTransition(OBSWeakSource scene_,

View File

@@ -24,6 +24,7 @@
constexpr auto default_interval = 300;
constexpr auto previous_scene_name = "Previous Scene";
constexpr auto current_transition_name = "Current Transition";
typedef enum { NO_SWITCH = 0, SWITCH = 1, RANDOM_SWITCH = 2 } NoMatch;
typedef enum { PERSIST = 0, START = 1, STOP = 2 } StartupBehavior;
@@ -84,6 +85,7 @@ struct SwitcherData {
std::vector<std::string> ignoreWindowsSwitches;
bool uninterruptibleSceneSequenceActive = false;
std::deque<SceneSequenceSwitch> sceneSequenceSwitches;
std::deque<RandomSwitch> randomSwitches;
@@ -100,7 +102,6 @@ struct SwitcherData {
std::deque<SceneTransition> sceneTransitions;
std::deque<DefaultSceneTransition> defaultSceneTransitions;
bool checkedDefTransition = false;
std::deque<MediaSwitch> mediaSwitches;
@@ -160,13 +161,14 @@ struct SwitcherData {
void Stop();
bool sceneChangedDuringWait();
bool prioFuncsValid();
void writeSceneInfoToFile();
void writeToStatusFile(QString status);
bool checkPause();
void checkDefaultSceneTransitions(bool &match,
OBSWeakSource &transition);
void setCurrentDefTransition(OBSWeakSource &transition);
bool checkForMatch(OBSWeakSource &scene, OBSWeakSource &transition,
int &linger);
void checkSceneSequence(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &linger);
void checkIdleSwitch(bool &match, OBSWeakSource &scene,
@@ -195,6 +197,8 @@ struct SwitcherData {
OBSWeakSource &transition, int &sleep);
void checkSwitchCooldown(bool &match);
void checkTriggers();
bool checkPause();
void checkDefaultSceneTransitions();
void saveSettings(obs_data_t *obj);
void saveWindowTitleSwitches(obs_data_t *obj);

View File

@@ -51,27 +51,26 @@ OBSWeakSource SceneGroup::getNextSceneTime()
OBSWeakSource SceneGroup::getNextSceneRandom()
{
if (scenes.size() == 1) {
return *scenes.begin();
currentIdx = 0;
return scenes[currentIdx];
}
std::vector<OBSWeakSource> rs(scenes);
std::random_device rng;
std::mt19937 urng(rng());
std::shuffle(rs.begin(), rs.end(), urng);
for (OBSWeakSource &s : rs) {
if (s == lastRandomScene) {
continue;
}
int rIdx = lastRandomScene;
do {
rIdx = rand() % scenes.size();
} while (rIdx == lastRandomScene);
lastRandomScene = s;
lastRandomScene = rIdx;
currentIdx = rIdx;
return s;
}
return nullptr;
return scenes[currentIdx];
}
OBSWeakSource SceneGroup::getCurrentScene()
{
if (scenes.size() < currentIdx + 1) {
return nullptr;
}
return scenes[currentIdx];
}

View File

@@ -133,6 +133,15 @@ void SceneTrigger::logMatch()
case sceneTriggerAction::MUTE_SOURCE:
actionName = "MUTE (" + GetWeakSourceName(audioSource) + ")";
break;
case sceneTriggerAction::UNMUTE_SOURCE:
actionName = "UNMUTE (" + GetWeakSourceName(audioSource) + ")";
break;
case sceneTriggerAction::START_SWITCHER:
actionName = "START SCENE SWITCHER";
break;
case sceneTriggerAction::STOP_SWITCHER:
actionName = "STOP SCENE SWITCHER";
break;
default:
actionName = "UNKOWN";
break;
@@ -176,24 +185,35 @@ void frontEndActionThread(sceneTriggerAction action, double delay)
case sceneTriggerAction::STOP_REPLAY_BUFFER:
obs_frontend_replay_buffer_stop();
break;
case sceneTriggerAction::MUTE_SOURCE:
obs_frontend_replay_buffer_stop();
break;
default:
blog(LOG_WARNING, "ignoring unexpected frontend action '%d'",
action);
break;
}
}
void muteThread(OBSWeakSource source, double delay)
void muteThread(OBSWeakSource source, double delay, bool mute)
{
long long mil = delay * 1000;
std::this_thread::sleep_for(std::chrono::milliseconds(mil));
auto s = obs_weak_source_get_source(source);
obs_source_set_muted(s, true);
obs_source_set_muted(s, mute);
obs_source_release(s);
}
void statusThread(double delay, bool stop)
{
long long mil = delay * 1000;
std::this_thread::sleep_for(std::chrono::milliseconds(mil));
if (stop) {
switcher->Stop();
} else {
switcher->Start();
}
}
bool isFrontendAction(sceneTriggerAction triggerAction)
{
return triggerAction == sceneTriggerAction::START_RECORDING ||
@@ -206,6 +226,18 @@ bool isFrontendAction(sceneTriggerAction triggerAction)
triggerAction == sceneTriggerAction::STOP_REPLAY_BUFFER;
}
bool isAudioAction(sceneTriggerAction t)
{
return t == sceneTriggerAction::MUTE_SOURCE ||
t == sceneTriggerAction::UNMUTE_SOURCE;
}
bool isSwitcherStatusAction(sceneTriggerAction t)
{
return t == sceneTriggerAction::START_SWITCHER ||
t == sceneTriggerAction::STOP_SWITCHER;
}
void SceneTrigger::performAction()
{
if (triggerAction == sceneTriggerAction::NONE) {
@@ -216,20 +248,22 @@ void SceneTrigger::performAction()
if (isFrontendAction(triggerAction)) {
t = std::thread(frontEndActionThread, triggerAction, duration);
} else if (isAudioAction(triggerAction)) {
bool mute = triggerAction == sceneTriggerAction::MUTE_SOURCE;
t = std::thread(muteThread, audioSource, duration, mute);
} else if (isSwitcherStatusAction(triggerAction)) {
bool stop = triggerAction == sceneTriggerAction::STOP_SWITCHER;
t = std::thread(statusThread, duration, stop);
} else {
t = std::thread(muteThread, audioSource, duration);
blog(LOG_WARNING, "ignoring unkown action '%d'", triggerAction);
}
t.detach();
}
bool SceneTrigger::checkMatch(OBSWeakSource previousScene)
bool SceneTrigger::checkMatch(OBSWeakSource currentScene,
OBSWeakSource previousScene)
{
OBSSource source = obs_frontend_get_current_scene();
OBSWeakSource currentScene = obs_source_get_weak_source(source);
obs_source_release(source);
obs_weak_source_release(currentScene);
switch (triggerType) {
case sceneTriggerType::NONE:
return false;
@@ -245,16 +279,26 @@ bool SceneTrigger::checkMatch(OBSWeakSource previousScene)
void SwitcherData::checkTriggers()
{
if (SceneTrigger::pause || stop) {
if (SceneTrigger::pause) {
return;
}
OBSSource source = obs_frontend_get_current_scene();
OBSWeakSource currentScene = obs_source_get_weak_source(source);
for (auto &t : sceneTriggers) {
if (t.checkMatch(previousScene)) {
if (stop && !isSwitcherStatusAction(t.triggerAction)) {
continue;
}
if (t.checkMatch(currentScene, previousScene)) {
t.logMatch();
t.performAction();
}
}
obs_source_release(source);
obs_weak_source_release(currentScene);
}
void SwitcherData::saveSceneTriggers(obs_data_t *obj)
@@ -425,10 +469,13 @@ void SceneTrigger::load(obs_data_t *obj)
audioSource = GetWeakSourceByName(audioSourceName);
}
inline void populateTriggers(QComboBox *list)
static inline void populateTriggers(QComboBox *list)
{
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none"));
AdvSceneSwitcher::addSelectionEntry(
list,
obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive"));
list->addItem(obs_module_text(
@@ -439,8 +486,11 @@ inline void populateTriggers(QComboBox *list)
inline void populateActions(QComboBox *list)
{
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none"));
AdvSceneSwitcher::addSelectionEntry(
list,
obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording"));
list->addItem(obs_module_text(
@@ -459,6 +509,12 @@ inline void populateActions(QComboBox *list)
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher"));
list->addItem(obs_module_text(
"AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher"));
}
SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s)
@@ -495,7 +551,7 @@ SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s)
audioSources->setCurrentText(
GetWeakSourceName(s->audioSource).c_str());
if (s->triggerAction == sceneTriggerAction::MUTE_SOURCE) {
if (isAudioAction(s->triggerAction)) {
audioSources->show();
} else {
audioSources->hide();
@@ -560,7 +616,7 @@ void SceneTriggerWidget::TriggerActionChanged(int index)
static_cast<sceneTriggerAction>(index);
}
if (switchData->triggerAction == sceneTriggerAction::MUTE_SOURCE) {
if (isAudioAction(switchData->triggerAction)) {
audioSources->show();
} else {
audioSources->hide();

View File

@@ -118,29 +118,37 @@ void SwitcherData::writeToStatusFile(QString msg)
void SwitcherData::checkSwitchInfoFromFile(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition)
{
if (!fileIO.readEnabled || fileIO.readPath.empty()) {
if (!fileIO.readEnabled || fileIO.readPath.empty() ||
FileSwitch::pause) {
return;
}
QFile file(QString::fromStdString(fileIO.readPath));
if (file.open(QIODevice::ReadOnly)) {
QTextStream in(&file);
QString sceneStr = in.readLine();
obs_source_t *sceneRead =
obs_get_source_by_name(sceneStr.toUtf8().constData());
if (sceneRead) {
obs_weak_source_t *sceneReadWs =
obs_source_get_weak_source(sceneRead);
match = true;
scene = sceneReadWs;
transition = nullptr;
obs_weak_source_release(sceneReadWs);
obs_source_release(sceneRead);
}
file.close();
if (!file.open(QIODevice::ReadOnly)) {
return;
}
QTextStream in(&file);
QString sceneStr = in.readLine();
OBSWeakSource sceneRead = GetWeakSourceByQString(sceneStr);
QString transitionStr = in.readLine();
OBSWeakSource transitionRead =
GetWeakTransitionByQString(transitionStr);
if (sceneRead) {
match = true;
scene = sceneRead;
transition = transitionRead;
vblog(LOG_INFO,
"match for 'file' - read scene '%s' and transition '%s' from file '%s'",
sceneStr.toUtf8().constData(),
transitionStr.toUtf8().constData(),
fileIO.readPath.c_str());
}
file.close();
}
static size_t WriteCallback(void *contents, size_t size, size_t nmemb,

View File

@@ -5,14 +5,15 @@ bool SceneSwitcherEntry::initialized()
{
return (usePreviousScene || WeakSourceValid(scene) ||
SceneGroupValid(group)) &&
transition;
(useCurrentTransition || transition);
}
bool SceneSwitcherEntry::valid()
{
return !initialized() || ((usePreviousScene || WeakSourceValid(scene) ||
SceneGroupValid(group)) &&
WeakSourceValid(transition));
return !initialized() ||
((usePreviousScene || WeakSourceValid(scene) ||
SceneGroupValid(group)) &&
(useCurrentTransition || WeakSourceValid(transition)));
}
void SceneSwitcherEntry::logMatchScene()
@@ -82,8 +83,11 @@ void SceneSwitcherEntry::save(obs_data_t *obj, const char *targetTypeSaveName,
obs_data_set_string(obj, targetSaveName, targetName.c_str());
obs_data_set_string(obj, transitionSaveName,
GetWeakSourceName(transition).c_str());
std::string transitionName = current_transition_name;
if (!useCurrentTransition) {
transitionName = GetWeakSourceName(transition);
}
obs_data_set_string(obj, transitionSaveName, transitionName.c_str());
}
void SceneSwitcherEntry::load(obs_data_t *obj, const char *targetTypeLoadName,
@@ -104,11 +108,14 @@ void SceneSwitcherEntry::load(obs_data_t *obj, const char *targetTypeLoadName,
group = GetSceneGroupByName(targetName);
}
usePreviousScene = strcmp(targetName, previous_scene_name) == 0;
const char *transitionName =
obs_data_get_string(obj, transitionLoadName);
transition = GetWeakTransitionByName(transitionName);
usePreviousScene = strcmp(targetName, previous_scene_name) == 0;
useCurrentTransition =
strcmp(transitionName, current_transition_name) == 0;
}
void SwitchWidget::SceneGroupAdd(const QString &name)
@@ -166,7 +173,8 @@ void SwitchWidget::SceneGroupRename(const QString &oldName,
}
SwitchWidget::SwitchWidget(QWidget *parent, SceneSwitcherEntry *s,
bool usePreviousScene, bool addSceneGroup)
bool usePreviousScene, bool addSceneGroup,
bool addCurrentTransition)
{
scenes = new QComboBox();
transitions = new QComboBox();
@@ -194,7 +202,8 @@ SwitchWidget::SwitchWidget(QWidget *parent, SceneSwitcherEntry *s,
AdvSceneSwitcher::populateSceneSelection(scenes, usePreviousScene,
addSceneGroup);
AdvSceneSwitcher::populateTransitionSelection(transitions);
AdvSceneSwitcher::populateTransitionSelection(transitions,
addCurrentTransition);
switchData = s;
showSwitchData();
@@ -218,6 +227,10 @@ void SwitchWidget::showSwitchData()
transitions->setCurrentText(
GetWeakSourceName(switchData->transition).c_str());
if (switchData->useCurrentTransition) {
transitions->setCurrentText(
obs_module_text("AdvSceneSwitcher.currentTransition"));
}
if (switchData->usePreviousScene) {
scenes->setCurrentText(obs_module_text(
@@ -278,4 +291,5 @@ void SwitchWidget::TransitionChanged(const QString &text)
std::lock_guard<std::mutex> lock(switcher->m);
switchData->transition = GetWeakTransitionByQString(text);
switchData->useCurrentTransition = switchData->transition == nullptr;
}

View File

@@ -353,7 +353,8 @@ ScreenRegionWidget::ScreenRegionWidget(QWidget *parent, ScreenRegionSwitch *s)
AdvSceneSwitcher::populateSceneSelection(
excludeScenes, false, false, true,
obs_module_text(
"AdvSceneSwitcher.screenRegionTab.excludeScenes.None"));
"AdvSceneSwitcher.screenRegionTab.excludeScenes.None"),
true);
if (s) {
excludeScenes->setCurrentText(

View File

@@ -4,6 +4,8 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
constexpr auto max_extend_text_size = 150;
bool SceneSequenceSwitch::pause = false;
static QMetaObject::Connection addPulse;
@@ -159,30 +161,41 @@ void AdvSceneSwitcher::on_sceneSequenceLoad_clicked()
close();
}
bool matchInterruptible(SwitcherData *switcher, SceneSequenceSwitch &s)
void AdvSceneSwitcher::OpenSequenceExtendEdit(SequenceWidget *sw)
{
bool durationReached = s.matchCount * (switcher->interval / 1000.0) >=
s.delay;
QDialog edit;
SequenceWidget editWidget(this, sw->getSwitchData(), false, true);
QHBoxLayout layout;
layout.setSizeConstraint(QLayout::SetFixedSize);
layout.addWidget(&editWidget);
edit.setLayout(&layout);
edit.setWindowTitle(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.extendEdit"));
edit.exec();
s.matchCount++;
if (durationReached) {
return true;
}
return false;
sw->UpdateExtendText();
}
bool matchUninterruptible(SwitcherData *switcher, SceneSequenceSwitch &s,
obs_source_t *currentSource, int &linger)
void AdvSceneSwitcher::on_sequenceEdit_clicked()
{
// scene was already active for the previous cycle so remove this time
int dur = s.delay * 1000 - switcher->interval;
if (dur > 0) {
switcher->waitScene = currentSource;
linger = dur;
int index = ui->sceneSequenceSwitches->currentRow();
if (index == -1) {
return;
}
return true;
SequenceWidget *currentWidget =
(SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(
ui->sceneSequenceSwitches->item(index));
OpenSequenceExtendEdit(currentWidget);
}
void AdvSceneSwitcher::on_sceneSequenceSwitches_itemDoubleClicked(
QListWidgetItem *item)
{
SequenceWidget *currentWidget =
(SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(item);
OpenSequenceExtendEdit(currentWidget);
}
void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene,
@@ -192,38 +205,53 @@ void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene,
return;
}
obs_source_t *currentSource = obs_frontend_get_current_scene();
obs_weak_source_t *ws = obs_source_get_weak_source(currentSource);
obs_source_t *currentSceneSource = obs_frontend_get_current_scene();
obs_weak_source_t *currentScene =
obs_source_get_weak_source(currentSceneSource);
for (SceneSequenceSwitch &s : sceneSequenceSwitches) {
if (!s.initialized()) {
// Continue the active uninterruptible sequence and skip others
if (uninterruptibleSceneSequenceActive &&
s.activeSequence == nullptr) {
continue;
}
if (s.startScene == ws) {
if (!match) {
if (s.interruptible) {
match = matchInterruptible(switcher, s);
} else {
match = matchUninterruptible(
switcher, s, currentSource,
linger);
}
bool matched = s.checkMatch(currentScene, linger);
if (match) {
scene = s.getScene();
transition = s.transition;
if (switcher->verbose) {
s.logMatch();
}
if (!match && matched) {
match = matched;
if (s.activeSequence) {
scene = s.activeSequence->getScene();
transition = s.activeSequence->transition;
} else {
scene = s.getScene();
transition = s.transition;
if (verbose) {
s.logMatch();
}
}
} else {
s.matchCount = 0;
s.advanceActiveSequence();
if (verbose) {
s.logAdvanceSequence();
}
// Ignore other switching methods if sequence is not
// interruptible and has not reached its end
if (s.activeSequence) {
uninterruptibleSceneSequenceActive =
!s.interruptible;
}
}
}
obs_source_release(currentSource);
obs_weak_source_release(ws);
if (!match) {
uninterruptibleSceneSequenceActive = false;
}
obs_source_release(currentSceneSource);
obs_weak_source_release(currentScene);
}
void SwitcherData::saveSceneSequenceSwitches(obs_data_t *obj)
@@ -291,18 +319,32 @@ bool SceneSequenceSwitch::valid()
(SceneSwitcherEntry::valid() && WeakSourceValid(startScene));
}
void SceneSequenceSwitch::save(obs_data_t *obj)
void SceneSequenceSwitch::save(obs_data_t *obj, bool saveExt)
{
SceneSwitcherEntry::save(obj);
obs_data_set_int(obj, "startTargetType",
static_cast<int>(startTargetType));
obs_data_set_string(obj, "startScene",
GetWeakSourceName(startScene).c_str());
obs_data_set_double(obj, "delay", delay);
obs_data_set_int(obj, "delayMultiplier", delayMultiplier);
obs_data_set_bool(obj, "interruptible", interruptible);
if (saveExt) {
auto cur = extendedSequence.get();
obs_data_array_t *extendScenes = obs_data_array_create();
while (cur) {
obs_data_t *array_obj = obs_data_create();
cur->save(array_obj, false);
obs_data_array_push_back(extendScenes, array_obj);
obs_data_release(array_obj);
cur = cur->extendedSequence.get();
}
obs_data_set_array(obj, "extendScenes", extendScenes);
obs_data_array_release(extendScenes);
}
}
// To be removed in future version
@@ -341,14 +383,15 @@ bool loadOldScequence(obs_data_t *obj, SceneSequenceSwitch *s)
return true;
}
void SceneSequenceSwitch::load(obs_data_t *obj)
void SceneSequenceSwitch::load(obs_data_t *obj, bool saveExt)
{
if (loadOldScequence(obj, this)) {
return;
}
SceneSwitcherEntry::load(obj);
startTargetType = static_cast<SwitchTargetType>(
obs_data_get_int(obj, "startTargetType"));
const char *scene = obs_data_get_string(obj, "startScene");
startScene = GetWeakSourceByName(scene);
@@ -360,6 +403,174 @@ void SceneSequenceSwitch::load(obs_data_t *obj)
delayMultiplier = 1;
interruptible = obs_data_get_bool(obj, "interruptible");
if (saveExt) {
auto cur = this;
obs_data_array_t *extendScenes =
obs_data_get_array(obj, "extendScenes");
size_t count = obs_data_array_count(extendScenes);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj =
obs_data_array_item(extendScenes, i);
cur->extendedSequence =
std::make_unique<SceneSequenceSwitch>();
cur->extendedSequence->load(array_obj, false);
cur = cur->extendedSequence.get();
obs_data_release(array_obj);
}
obs_data_array_release(extendScenes);
}
}
bool SceneSequenceSwitch::reduce()
{
// Reset activeSequence just in case it was one of the deleted entries
activeSequence = nullptr;
if (!extendedSequence) {
return true;
}
if (extendedSequence->reduce()) {
extendedSequence.reset(nullptr);
}
return false;
}
SceneSequenceSwitch *SceneSequenceSwitch::extend()
{
if (extendedSequence) {
return extendedSequence->extend();
}
extendedSequence = std::make_unique<SceneSequenceSwitch>();
extendedSequence->startScene = scene;
return extendedSequence.get();
}
bool SceneSequenceSwitch::checkMatch(OBSWeakSource currentScene, int &linger,
SceneSequenceSwitch *root)
{
if (!initialized()) {
if (root) {
root->activeSequence = nullptr;
}
return false;
}
bool match = false;
if (activeSequence) {
return activeSequence->checkMatch(currentScene, linger, this);
}
if (startScene == currentScene) {
if (interruptible) {
match = checkDurationMatchInterruptible();
} else {
match = true;
prepareUninterruptibleMatch(currentScene, linger);
}
} else {
matchCount = 0;
if (root) {
root->activeSequence = nullptr;
logSequenceCanceled();
}
}
return match;
}
bool SceneSequenceSwitch::checkDurationMatchInterruptible()
{
bool durationReached = matchCount * (switcher->interval / 1000.0) >=
delay;
matchCount++;
if (durationReached) {
matchCount = 0;
return true;
}
return false;
}
void SceneSequenceSwitch::prepareUninterruptibleMatch(
OBSWeakSource currentScene, int &linger)
{
int dur = delay * 1000;
if (dur > 0) {
switcher->waitScene = obs_weak_source_get_source(currentScene);
obs_source_release(switcher->waitScene);
linger = dur;
}
}
void SceneSequenceSwitch::advanceActiveSequence()
{
// Set start Scene
OBSWeakSource currentSceneGroupScene = nullptr;
if (targetType == SwitchTargetType::SceneGroup && group) {
currentSceneGroupScene = group->getCurrentScene();
}
if (activeSequence) {
activeSequence = activeSequence->extendedSequence.get();
} else {
activeSequence = extendedSequence.get();
}
if (activeSequence) {
if (activeSequence->startTargetType ==
SwitchTargetType::SceneGroup) {
activeSequence->startScene = currentSceneGroupScene;
}
if (activeSequence->targetType == SwitchTargetType::Scene &&
!activeSequence->scene) {
blog(LOG_WARNING,
"cannot advance sequence - null scene set");
activeSequence = nullptr;
return;
}
if (activeSequence->targetType ==
SwitchTargetType::SceneGroup &&
activeSequence->group &&
activeSequence->group->scenes.empty()) {
blog(LOG_WARNING,
"cannot advance sequence - no scenes specified in '%s'",
activeSequence->group->name.c_str());
activeSequence = nullptr;
return;
}
// Reinit old matchCount value in case it was previously set
activeSequence->matchCount = 0;
}
}
void SceneSequenceSwitch::logAdvanceSequence()
{
if (activeSequence) {
std::string targetName =
GetWeakSourceName(activeSequence->scene);
if (activeSequence->targetType ==
SwitchTargetType::SceneGroup &&
activeSequence->group) {
targetName = activeSequence->group->name;
}
blog(LOG_INFO, "continuing sequence with '%s' -> '%s'",
GetWeakSourceName(activeSequence->startScene).c_str(),
targetName.c_str());
}
}
void SceneSequenceSwitch::logSequenceCanceled()
{
blog(LOG_INFO, "unexpected scene change - cancel sequence");
}
void populateDelayUnits(QComboBox *list)
@@ -369,14 +580,82 @@ void populateDelayUnits(QComboBox *list)
list->addItem(obs_module_text("AdvSceneSwitcher.unit.hours"));
}
SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s)
: SwitchWidget(parent, s, true, true)
QString makeExtendText(SceneSequenceSwitch *s, int curLen = 0)
{
if (!s) {
return "";
}
QString ext = "";
ext = QString::number(s->delay / s->delayMultiplier) + " ";
switch (s->delayMultiplier) {
case 1:
ext += obs_module_text("AdvSceneSwitcher.unit.secends");
break;
case 60:
ext += obs_module_text("AdvSceneSwitcher.unit.minutes");
break;
case 60 * 60:
ext += obs_module_text("AdvSceneSwitcher.unit.hours");
break;
default:
ext += obs_module_text("?????");
break;
}
QString sceneName = GetWeakSourceName(s->scene).c_str();
if (s->targetType == SwitchTargetType::SceneGroup && s->group) {
sceneName = QString::fromStdString(s->group->name);
}
if (sceneName.isEmpty()) {
sceneName = obs_module_text("AdvSceneSwitcher.selectScene");
}
ext += " -> [" + sceneName + "]";
if (ext.length() + curLen > max_extend_text_size) {
return "...";
}
if (s->extendedSequence.get()) {
return ext +=
" | " + makeExtendText(s->extendedSequence.get(),
curLen + ext.length());
} else {
return ext;
}
}
SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
bool extendSequence, bool editExtendMode)
: SwitchWidget(parent, s, !extendSequence, true)
{
this->setParent(parent);
delay = new QDoubleSpinBox();
delayUnits = new QComboBox();
startScenes = new QComboBox();
interruptible = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.interruptible"));
extendText = new QLabel();
extend = new QPushButton();
reduce = new QPushButton();
extend->setProperty("themeID",
QVariant(QStringLiteral("addIconSmall")));
reduce->setProperty("themeID",
QVariant(QStringLiteral("removeIconSmall")));
extend->setMaximumSize(22, 22);
reduce->setMaximumSize(22, 22);
// We need to extend the generic SwitchWidget::SceneChanged()
// with our own SequenceWidget::SceneChanged()
// so the old singal / slot needs to be disconnected
scenes->disconnect();
QWidget::connect(scenes, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SceneChanged(const QString &)));
QWidget::connect(delay, SIGNAL(valueChanged(double)), this,
SLOT(DelayChanged(double)));
@@ -388,6 +667,11 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s)
QWidget::connect(interruptible, SIGNAL(stateChanged(int)), this,
SLOT(InterruptibleChanged(int)));
QWidget::connect(extend, SIGNAL(clicked()), this,
SLOT(ExtendClicked()));
QWidget::connect(reduce, SIGNAL(clicked()), this,
SLOT(ReduceClicked()));
delay->setMaximum(99999.000000);
AdvSceneSwitcher::populateSceneSelection(startScenes, false);
populateDelayUnits(delayUnits);
@@ -413,17 +697,62 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s)
interruptible->setChecked(s->interruptible);
}
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{startScenes}}", startScenes},
{"{{scenes}}", scenes},
{"{{delay}}", delay},
{"{{delayUnits}}", delayUnits},
{"{{transitions}}", transitions},
{"{{interruptible}}", interruptible}};
placeWidgets(obs_module_text("AdvSceneSwitcher.sceneSequenceTab.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
if (extendSequence) {
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", scenes},
{"{{delay}}", delay},
{"{{delayUnits}}", delayUnits},
{"{{transitions}}", transitions}};
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.extendEntry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
} else {
QHBoxLayout *startSequence = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{startScenes}}", startScenes},
{"{{scenes}}", scenes},
{"{{delay}}", delay},
{"{{delayUnits}}", delayUnits},
{"{{transitions}}", transitions},
{"{{interruptible}}", interruptible}};
placeWidgets(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.entry"),
startSequence, widgetPlaceholders);
//exetend widgets placed here
extendSequenceLayout = new QVBoxLayout;
if (s) {
if (!editExtendMode) {
extendText->setText(makeExtendText(
s->extendedSequence.get()));
} else {
auto cur = s->extendedSequence.get();
while (cur != nullptr) {
extendSequenceLayout->addWidget(
new SequenceWidget(parent, cur,
true, true));
cur = cur->extendedSequence.get();
}
}
}
QHBoxLayout *extendSequenceControlsLayout = new QHBoxLayout;
if (editExtendMode) {
extendSequenceControlsLayout->addWidget(extend);
extendSequenceControlsLayout->addWidget(reduce);
}
extendSequenceControlsLayout->addStretch();
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(startSequence);
mainLayout->addLayout(extendSequenceLayout);
mainLayout->addWidget(extendText);
mainLayout->addLayout(extendSequenceControlsLayout);
setLayout(mainLayout);
}
switchData = s;
@@ -493,6 +822,32 @@ void SequenceWidget::DelayUnitsChanged(int idx)
UpdateDelay();
}
void SequenceWidget::setExtendedSequenceStartScene()
{
switchData->extendedSequence->startScene = switchData->scene;
switchData->extendedSequence->startTargetType = SwitchTargetType::Scene;
if (switchData->targetType == SwitchTargetType::SceneGroup) {
switchData->extendedSequence->startScene = nullptr;
switchData->extendedSequence->startTargetType =
SwitchTargetType::SceneGroup;
}
}
void SequenceWidget::SceneChanged(const QString &text)
{
if (loading || !switchData) {
return;
}
SwitchWidget::SceneChanged(text);
std::lock_guard<std::mutex> lock(switcher->m);
if (switchData->extendedSequence) {
setExtendedSequenceStartScene();
}
}
void SequenceWidget::StartSceneChanged(const QString &text)
{
if (loading || !switchData) {
@@ -511,4 +866,45 @@ void SequenceWidget::InterruptibleChanged(int state)
std::lock_guard<std::mutex> lock(switcher->m);
switchData->interruptible = state;
auto cur = switchData->extendedSequence.get();
while (cur != nullptr) {
cur->interruptible = state;
cur = cur->extendedSequence.get();
}
}
void SequenceWidget::UpdateExtendText()
{
extendText->setText(makeExtendText(switchData->extendedSequence.get()));
}
void SequenceWidget::ExtendClicked()
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
auto es = switchData->extend();
SequenceWidget *ew = new SequenceWidget(this->parentWidget(), es, true);
extendSequenceLayout->addWidget(ew);
}
void SequenceWidget::ReduceClicked()
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->reduce();
int count = extendSequenceLayout->count();
auto item = extendSequenceLayout->takeAt(count - 1);
if (item) {
item->widget()->setVisible(false);
delete item;
}
}

View File

@@ -241,7 +241,7 @@ void TimeSwitch::load(obs_data_t *obj)
time = QTime::fromString(obs_data_get_string(obj, "time"));
}
inline void populateTriggers(QComboBox *list)
static inline void populateTriggers(QComboBox *list)
{
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.anyDay"));
list->addItem(obs_module_text("AdvSceneSwitcher.timeTab.mondays"));

View File

@@ -1,3 +1,5 @@
#include <thread>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
@@ -148,42 +150,28 @@ void AdvSceneSwitcher::on_defaultTransitionsDown_clicked()
switcher->defaultSceneTransitions[index + 1]);
}
void SwitcherData::checkDefaultSceneTransitions(bool &match,
OBSWeakSource &transition)
void SwitcherData::checkDefaultSceneTransitions()
{
if (checkedDefTransition || DefaultSceneTransition::pause) {
if (DefaultSceneTransition::pause || stop) {
return;
}
obs_source_t *currentSource = obs_frontend_get_current_scene();
obs_weak_source_t *ws = obs_source_get_weak_source(currentSource);
for (DefaultSceneTransition &s : defaultSceneTransitions) {
if (s.scene == ws) {
if (!s.initialized()) {
continue;
}
match = true;
transition = s.transition;
obs_source_t *currentSceneSource = obs_frontend_get_current_scene();
obs_weak_source_t *currentScene =
obs_source_get_weak_source(currentSceneSource);
for (auto &t : defaultSceneTransitions) {
if (t.checkMatch(currentScene)) {
if (verbose) {
s.logMatch();
t.logMatch();
}
t.setTransition();
break;
}
}
obs_source_release(currentSource);
obs_weak_source_release(ws);
checkedDefTransition = true;
}
void SwitcherData::setCurrentDefTransition(OBSWeakSource &transition)
{
obs_source_t *transitionSource = obs_weak_source_get_source(transition);
obs_frontend_set_current_transition(transitionSource);
obs_source_release(transitionSource);
obs_weak_source_release(currentScene);
obs_source_release(currentSceneSource);
}
void AdvSceneSwitcher::on_transitionOverridecheckBox_stateChanged(int state)
@@ -439,7 +427,7 @@ bool SceneTransition::valid()
TransitionSwitchWidget::TransitionSwitchWidget(QWidget *parent,
SceneTransition *s)
: SwitchWidget(parent, s, false)
: SwitchWidget(parent, s, false, false, false)
{
scenes2 = new QComboBox();
@@ -498,7 +486,7 @@ void TransitionSwitchWidget::Scene2Changed(const QString &text)
DefTransitionSwitchWidget::DefTransitionSwitchWidget(QWidget *parent,
DefaultSceneTransition *s)
: SwitchWidget(parent, s, false)
: SwitchWidget(parent, s, false, false, false)
{
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
@@ -533,3 +521,37 @@ void DefTransitionSwitchWidget::swapSwitchData(DefTransitionSwitchWidget *s1,
s1->setSwitchData(s2->getSwitchData());
s2->setSwitchData(t);
}
bool DefaultSceneTransition::checkMatch(OBSWeakSource currentScene)
{
return scene == currentScene;
}
void setTransitionDelayed(OBSWeakSource transition)
{
// A hardcoded delay of 50 ms before switching transition type is
// necessary due to OBS_FRONTEND_EVENT_SCENE_CHANGED seemingly firing a
// bit too early and thus leading to canceled transitions.
//
// The same is to be the case for OBS_FRONTEND_EVENT_TRANSITION_STOPPED.
//
// 50 ms was chosen as it seems to avoid the problem mentioned above and
// becuase that is the minimum value which can be chosen for the scene
// switcher's check interval.
// Thus it can be made sure that the delayed setting of the transition
// does not interfere with any new scene changes triggered by the scene
// switcher
std::this_thread::sleep_for(std::chrono::milliseconds(50));
obs_source_t *transitionSource = obs_weak_source_get_source(transition);
obs_frontend_set_current_transition(transitionSource);
obs_source_release(transitionSource);
}
void DefaultSceneTransition::setTransition()
{
std::thread t;
t = std::thread(setTransitionDelayed, transition);
t.detach();
}

View File

@@ -43,6 +43,16 @@ void SwitcherData::Prune()
sceneSequenceSwitches.erase(
sceneSequenceSwitches.begin() + i--);
}
auto cur = &s;
while (cur != nullptr) {
if (cur->extendedSequence &&
!cur->extendedSequence->valid()) {
cur->extendedSequence.reset(nullptr);
s.activeSequence = nullptr;
}
cur = cur->extendedSequence.get();
}
}
for (size_t i = 0; i < sceneTransitions.size(); i++) {