From 49bf1ad379d95448de442dc8ed915e8dd7343a10 Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Sat, 24 Aug 2024 10:36:16 +0200 Subject: [PATCH] Fix transition condition timing issues The condition type was able to miss transition starts of very fast conditions (e.g. Cut) when the selection "Any transition" was made. In that case no signal handler was installed (as the condition type might change) and only the transition scenes were compared at the time of the condition check. If that moment where the transition scenes differ was missed no transition start was recognized. Additionally, the "Transitioning to" and "Transitioning from" checks faced a similar issue, if the scene transition was happening so fast, that the condition check was not performed during the time of the transition. --- data/locale/en-US.ini | 1 + plugins/base/macro-condition-transition.cpp | 341 +++++++++++++------- plugins/base/macro-condition-transition.hpp | 38 ++- 3 files changed, 248 insertions(+), 132 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index e3334fbb..df091057 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -553,6 +553,7 @@ AdvSceneSwitcher.condition.transition.type.current="Current transition type is" AdvSceneSwitcher.condition.transition.type.duration="Current transition duration is" AdvSceneSwitcher.condition.transition.type.started="Transition started" AdvSceneSwitcher.condition.transition.type.ended="Transition ended" +AdvSceneSwitcher.condition.transition.type.videoEnded="Transition video ended" AdvSceneSwitcher.condition.transition.type.transitionSource="Transitioning from" AdvSceneSwitcher.condition.transition.type.transitionTarget="Transitioning to" AdvSceneSwitcher.condition.transition.durationSuffix="seconds" diff --git a/plugins/base/macro-condition-transition.cpp b/plugins/base/macro-condition-transition.cpp index 77f7d4bf..d25f0487 100644 --- a/plugins/base/macro-condition-transition.cpp +++ b/plugins/base/macro-condition-transition.cpp @@ -2,8 +2,6 @@ #include "layout-helpers.hpp" #include "scene-switch-helpers.hpp" -#include - namespace advss { const std::string MacroConditionTransition::id = "transition"; @@ -13,131 +11,188 @@ bool MacroConditionTransition::_registered = MacroConditionFactory::Register( {MacroConditionTransition::Create, MacroConditionTransitionEdit::Create, "AdvSceneSwitcher.condition.transition"}); -const static std::map filterConditionTypes = { - {TransitionCondition::CURRENT, - "AdvSceneSwitcher.condition.transition.type.current"}, - {TransitionCondition::DURATION, - "AdvSceneSwitcher.condition.transition.type.duration"}, - {TransitionCondition::STARTED, - "AdvSceneSwitcher.condition.transition.type.started"}, - {TransitionCondition::ENDED, - "AdvSceneSwitcher.condition.transition.type.ended"}, - {TransitionCondition::TRANSITION_SOURCE, - "AdvSceneSwitcher.condition.transition.type.transitionSource"}, - {TransitionCondition::TRANSITION_TARGET, - "AdvSceneSwitcher.condition.transition.type.transitionTarget"}, +const static std::map + conditionTypes = { + {MacroConditionTransition::Condition::CURRENT, + "AdvSceneSwitcher.condition.transition.type.current"}, + {MacroConditionTransition::Condition::DURATION, + "AdvSceneSwitcher.condition.transition.type.duration"}, + {MacroConditionTransition::Condition::STARTED, + "AdvSceneSwitcher.condition.transition.type.started"}, + {MacroConditionTransition::Condition::ENDED, + "AdvSceneSwitcher.condition.transition.type.ended"}, + {MacroConditionTransition::Condition::VIDEO_ENDED, + "AdvSceneSwitcher.condition.transition.type.videoEnded"}, + {MacroConditionTransition::Condition::TRANSITION_SOURCE, + "AdvSceneSwitcher.condition.transition.type.transitionSource"}, + {MacroConditionTransition::Condition::TRANSITION_TARGET, + "AdvSceneSwitcher.condition.transition.type.transitionTarget"}, }; -static bool isCurrentTransition(OBSWeakSource &t) +static bool isCurrentTransition(OBSWeakSource &transition) { - bool match; - auto tSource = obs_frontend_get_current_transition(); - auto tWeakSource = obs_source_get_weak_source(tSource); - match = t == tWeakSource; - obs_weak_source_release(tWeakSource); - obs_source_release(tSource); - return match; + OBSSourceAutoRelease source = obs_frontend_get_current_transition(); + OBSWeakSourceAutoRelease weakSource = + obs_source_get_weak_source(source); + return transition == weakSource; } -static bool isTargetScene(OBSWeakSource &target) +static bool isTargetScene(OBSWeakSource &targetScene) { - auto source = obs_frontend_get_current_scene(); - auto targetScene = obs_source_get_weak_source(source); - bool ret = target == targetScene; - obs_weak_source_release(targetScene); - obs_source_release(source); - return ret; + OBSSourceAutoRelease source = obs_frontend_get_current_scene(); + OBSWeakSourceAutoRelease scene = obs_source_get_weak_source(source); + return targetScene == scene; +} + +MacroConditionTransition::MacroConditionTransition(Macro *m) : MacroCondition(m) +{ + obs_frontend_add_event_callback(HandleFrontendEvent, this); +} + +MacroConditionTransition::~MacroConditionTransition() +{ + obs_frontend_remove_event_callback(HandleFrontendEvent, this); +} + +static bool sceneListContainsScene(const std::vector &scenes, + const OBSWeakSource &sceneToCheck) +{ + for (const auto &scene : scenes) { + if (scene == sceneToCheck) { + return true; + } + } + return false; } bool MacroConditionTransition::CheckCondition() { - bool anyTransitionEnded = _lastTransitionEndTime != - GetLastTransitionEndTime(); - bool anyTransitionStarted = AnySceneTransitionStarted(); - bool transitionStarted = false; - bool transitionEnded = false; - - if (_transition.GetType() == TransitionSelection::Type::ANY) { - transitionStarted = anyTransitionStarted; - transitionEnded = anyTransitionEnded; - } else { - transitionStarted = _started; - transitionEnded = _ended; - } + std::lock_guard lock(_mutex); bool ret = false; switch (_condition) { - case TransitionCondition::CURRENT: { + case Condition::CURRENT: { auto transition = _transition.GetTransition(); ret = isCurrentTransition(transition); break; } - case TransitionCondition::DURATION: + case Condition::DURATION: ret = _duration.Milliseconds() == obs_frontend_get_transition_duration(); break; - case TransitionCondition::STARTED: - ret = transitionStarted; + case Condition::STARTED: + ret = _started; break; - case TransitionCondition::ENDED: - ret = transitionEnded; + case Condition::ENDED: + ret = _ended; break; - case TransitionCondition::TRANSITION_SOURCE: - ret = anyTransitionStarted && - _scene.GetScene() == GetCurrentScene(); + case Condition::VIDEO_ENDED: + ret = _videoEnded; break; - case TransitionCondition::TRANSITION_TARGET: { - auto scene = _scene.GetScene(); - ret = anyTransitionStarted && isTargetScene(scene); + case Condition::TRANSITION_SOURCE: + ret = sceneListContainsScene(_transitionStartScenes, + _scene.GetScene()); + break; + case Condition::TRANSITION_TARGET: + ret = sceneListContainsScene(_transitionEndScenes, + _scene.GetScene()); break; - } default: break; } // Reset for next interval - if (_started) { - _started = false; - } - if (_ended) { - _ended = false; - } - if (anyTransitionEnded) { - _lastTransitionEndTime = GetLastTransitionEndTime(); - } + _started = false; + _ended = false; + _videoEnded = false; + _transitionStartScenes.clear(); + _transitionEndScenes.clear(); return ret; } void MacroConditionTransition::ConnectToTransitionSignals() { - auto source = obs_weak_source_get_source(_transition.GetTransition()); - auto sh = obs_source_get_signal_handler(source); - signal_handler_connect(sh, "transition_start", TransitionStarted, this); - signal_handler_connect(sh, "transition_stop", TransitionEnded, this); - obs_source_release(source); + const bool useFrontendTransitionSelection = + _transition.GetType() != + TransitionSelection::Type::TRANSITION || + (_condition == Condition::TRANSITION_SOURCE || + _condition == Condition::TRANSITION_TARGET); + + _signals.clear(); + OBSSourceAutoRelease source = + useFrontendTransitionSelection + ? obs_frontend_get_current_transition() + : obs_weak_source_get_source( + _transition.GetTransition()); + signal_handler_t *sh = obs_source_get_signal_handler(source); + _signals.emplace_back(sh, "transition_start", TransitionStarted, this); + _signals.emplace_back(sh, "transition_stop", TransitionEnded, this); + _signals.emplace_back(sh, "transition_video_stop", TransitionVideoEnded, + this); } -void MacroConditionTransition::DisconnectTransitionSignals() +void MacroConditionTransition::TransitionStarted(void *data, calldata_t *cd) { - auto source = obs_weak_source_get_source(_transition.GetTransition()); - auto sh = obs_source_get_signal_handler(source); - signal_handler_disconnect(sh, "transition_start", TransitionStarted, - this); - signal_handler_disconnect(sh, "transition_stop", TransitionEnded, this); - obs_source_release(source); -} + auto *condition = static_cast(data); + if (!condition) { + return; + } -void MacroConditionTransition::TransitionStarted(void *data, calldata_t *) -{ - auto *transitionCond = static_cast(data); - transitionCond->_started = true; + auto transitionSource = (obs_source_t *)calldata_ptr(cd, "source"); + + OBSSourceAutoRelease startSource = obs_transition_get_source( + transitionSource, OBS_TRANSITION_SOURCE_A); + OBSSourceAutoRelease endSource = + obs_frontend_preview_program_mode_active() + ? obs_frontend_get_current_scene() + : obs_transition_get_source(transitionSource, + OBS_TRANSITION_SOURCE_B); + + std::lock_guard lock(condition->_mutex); + condition->_started = true; + condition->_transitionStartScenes.emplace_back( + OBSGetWeakRef(startSource)); + condition->_transitionEndScenes.emplace_back(OBSGetWeakRef(endSource)); } void MacroConditionTransition::TransitionEnded(void *data, calldata_t *) { - auto *transitionCond = static_cast(data); - transitionCond->_ended = true; + auto *condition = static_cast(data); + if (!condition) { + return; + } + + std::lock_guard lock(condition->_mutex); + condition->_ended = true; +} + +void MacroConditionTransition::TransitionVideoEnded(void *data, calldata_t *) +{ + auto *condition = static_cast(data); + if (!condition) { + return; + } + + std::lock_guard lock(condition->_mutex); + condition->_videoEnded = true; +} + +void MacroConditionTransition::HandleFrontendEvent(obs_frontend_event event, + void *data) +{ + auto condition = reinterpret_cast(data); + if (!condition) { + return; + } + + switch (event) { + case OBS_FRONTEND_EVENT_TRANSITION_CHANGED: + condition->ConnectToTransitionSignals(); + break; + default: + break; + }; } bool MacroConditionTransition::Save(obs_data_t *obj) const @@ -147,27 +202,66 @@ bool MacroConditionTransition::Save(obs_data_t *obj) const _transition.Save(obj); _scene.Save(obj); _duration.Save(obj); + obs_data_set_int(obj, "version", 1); return true; } bool MacroConditionTransition::Load(obs_data_t *obj) { MacroCondition::Load(obj); - _condition = static_cast( + _condition = static_cast( obs_data_get_int(obj, "condition")); + _transition.Load(obj); _scene.Load(obj); _duration.Load(obj); + + // Backwards compatibility + if (obs_data_get_int(obj, "version") < 1) { + enum class TransitionCondition { + CURRENT, + DURATION, + STARTED, + ENDED, + TRANSITION_SOURCE, + TRANSITION_TARGET, + }; + TransitionCondition oldValue = static_cast( + obs_data_get_int(obj, "condition")); + switch (oldValue) { + case TransitionCondition::CURRENT: + _condition = Condition::CURRENT; + break; + case TransitionCondition::DURATION: + _condition = Condition::DURATION; + break; + case TransitionCondition::STARTED: + _condition = Condition::STARTED; + break; + case TransitionCondition::ENDED: + _condition = Condition::ENDED; + break; + case TransitionCondition::TRANSITION_SOURCE: + _condition = Condition::TRANSITION_SOURCE; + break; + case TransitionCondition::TRANSITION_TARGET: + _condition = Condition::TRANSITION_TARGET; + break; + default: + break; + } + } + ConnectToTransitionSignals(); return true; } std::string MacroConditionTransition::GetShortDesc() const { - if (_condition == TransitionCondition::CURRENT || - _condition == TransitionCondition::DURATION || - _condition == TransitionCondition::STARTED || - _condition == TransitionCondition::ENDED) { + if (_condition == Condition::CURRENT || + _condition == Condition::DURATION || + _condition == Condition::STARTED || + _condition == Condition::ENDED) { return _transition.ToString(); } return ""; @@ -175,8 +269,9 @@ std::string MacroConditionTransition::GetShortDesc() const static inline void populateConditionSelection(QComboBox *list) { - for (auto entry : filterConditionTypes) { - list->addItem(obs_module_text(entry.second.c_str())); + for (const auto &[value, name] : conditionTypes) { + list->addItem(obs_module_text(name.c_str()), + static_cast(value)); } } @@ -204,18 +299,16 @@ MacroConditionTransitionEdit::MacroConditionTransitionEdit( QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)), this, SLOT(DurationChanged(const Duration &))); - QHBoxLayout *mainLayout = new QHBoxLayout; - std::unordered_map widgetPlaceholders = { - {"{{conditions}}", _conditions}, - {"{{transitions}}", _transitions}, - {"{{scenes}}", _scenes}, - {"{{duration}}", _duration}, - {"{{durationSuffix}}", _durationSuffix}, - }; + auto layout = new QHBoxLayout; PlaceWidgets( obs_module_text("AdvSceneSwitcher.condition.transition.entry"), - mainLayout, widgetPlaceholders); - setLayout(mainLayout); + layout, + {{"{{conditions}}", _conditions}, + {"{{transitions}}", _transitions}, + {"{{scenes}}", _scenes}, + {"{{duration}}", _duration}, + {"{{durationSuffix}}", _durationSuffix}}); + setLayout(layout); _entryData = entryData; UpdateEntryData(); @@ -231,7 +324,8 @@ void MacroConditionTransitionEdit::ConditionChanged(int index) { auto lock = LockContext(); _entryData->_condition = - static_cast(index); + static_cast( + _conditions->itemData(index).toInt()); } SetWidgetVisibility(); emit HeaderInfoChanged( @@ -246,7 +340,6 @@ void MacroConditionTransitionEdit::TransitionChanged( } auto lock = LockContext(); - _entryData->DisconnectTransitionSignals(); _entryData->_transition = t; _entryData->ConnectToTransitionSignals(); emit HeaderInfoChanged( @@ -280,22 +373,33 @@ void MacroConditionTransitionEdit::SetWidgetVisibility() } _transitions->setVisible( - _entryData->_condition == TransitionCondition::CURRENT || - _entryData->_condition == TransitionCondition::STARTED || - _entryData->_condition == TransitionCondition::ENDED); - _scenes->setVisible(_entryData->_condition == - TransitionCondition::TRANSITION_SOURCE || - _entryData->_condition == - TransitionCondition::TRANSITION_TARGET); + _entryData->_condition == + MacroConditionTransition::Condition::CURRENT || + _entryData->_condition == + MacroConditionTransition::Condition::STARTED || + _entryData->_condition == + MacroConditionTransition::Condition::ENDED || + _entryData->_condition == + MacroConditionTransition::Condition::VIDEO_ENDED); + _scenes->setVisible( + _entryData->_condition == + MacroConditionTransition::Condition::TRANSITION_SOURCE || + _entryData->_condition == + MacroConditionTransition::Condition::TRANSITION_TARGET); _duration->setVisible(_entryData->_condition == - TransitionCondition::DURATION); - _durationSuffix->setVisible(_entryData->_condition == - TransitionCondition::DURATION); + MacroConditionTransition::Condition::DURATION); + _durationSuffix->setVisible( + _entryData->_condition == + MacroConditionTransition::Condition::DURATION); bool addCurrent = _entryData->_condition == - TransitionCondition::DURATION; - bool addAny = _entryData->_condition == TransitionCondition::STARTED || - _entryData->_condition == TransitionCondition::ENDED; + MacroConditionTransition::Condition::DURATION; + bool addAny = _entryData->_condition == + MacroConditionTransition::Condition::STARTED || + _entryData->_condition == + MacroConditionTransition::Condition::ENDED || + _entryData->_condition == + MacroConditionTransition::Condition::VIDEO_ENDED; _transitions->Repopulate(addCurrent, addAny); } @@ -304,8 +408,11 @@ void MacroConditionTransitionEdit::UpdateEntryData() if (!_entryData) { return; } + SetWidgetVisibility(); - _conditions->setCurrentIndex(static_cast(_entryData->_condition)); + + _conditions->setCurrentIndex(_conditions->findData( + static_cast(_entryData->_condition))); _transitions->SetTransition(_entryData->_transition); _scenes->SetScene(_entryData->_scene); _duration->SetDuration(_entryData->_duration); diff --git a/plugins/base/macro-condition-transition.hpp b/plugins/base/macro-condition-transition.hpp index 54430c02..77efb2a0 100644 --- a/plugins/base/macro-condition-transition.hpp +++ b/plugins/base/macro-condition-transition.hpp @@ -4,23 +4,14 @@ #include "transition-selection.hpp" #include "scene-selection.hpp" -#include -#include +#include namespace advss { -enum class TransitionCondition { - CURRENT, - DURATION, - STARTED, - ENDED, - TRANSITION_SOURCE, - TRANSITION_TARGET, -}; - class MacroConditionTransition : public MacroCondition { public: - MacroConditionTransition(Macro *m) : MacroCondition(m) {} + MacroConditionTransition(Macro *m); + ~MacroConditionTransition(); bool CheckCondition(); bool Save(obs_data_t *obj) const; bool Load(obs_data_t *obj); @@ -31,9 +22,17 @@ public: return std::make_shared(m); } void ConnectToTransitionSignals(); - void DisconnectTransitionSignals(); - TransitionCondition _condition = TransitionCondition::CURRENT; + enum class Condition { + CURRENT, + DURATION, + STARTED = 50, + ENDED, + VIDEO_ENDED, + TRANSITION_SOURCE = 100, + TRANSITION_TARGET, + }; + Condition _condition = Condition::CURRENT; TransitionSelection _transition; SceneSelection _scene; Duration _duration; @@ -41,10 +40,19 @@ public: private: static void TransitionStarted(void *data, calldata_t *); static void TransitionEnded(void *data, calldata_t *); + static void TransitionVideoEnded(void *data, calldata_t *); + static void HandleFrontendEvent(enum obs_frontend_event event, void *); + + std::vector _signals; + + std::mutex _mutex; + std::vector _transitionStartScenes; + std::vector _transitionEndScenes; bool _started = false; bool _ended = false; - std::chrono::high_resolution_clock::time_point _lastTransitionEndTime{}; + bool _videoEnded = false; + static bool _registered; static const std::string id; };