SceneSwitcher/plugins/base/macro-condition-transition.cpp
WarmUpTill 49bf1ad379 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.
2024-09-24 23:00:47 +02:00

422 lines
12 KiB
C++

#include "macro-condition-transition.hpp"
#include "layout-helpers.hpp"
#include "scene-switch-helpers.hpp"
namespace advss {
const std::string MacroConditionTransition::id = "transition";
bool MacroConditionTransition::_registered = MacroConditionFactory::Register(
MacroConditionTransition::id,
{MacroConditionTransition::Create, MacroConditionTransitionEdit::Create,
"AdvSceneSwitcher.condition.transition"});
const static std::map<MacroConditionTransition::Condition, std::string>
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 &transition)
{
OBSSourceAutoRelease source = obs_frontend_get_current_transition();
OBSWeakSourceAutoRelease weakSource =
obs_source_get_weak_source(source);
return transition == weakSource;
}
static bool isTargetScene(OBSWeakSource &targetScene)
{
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<OBSWeakSource> &scenes,
const OBSWeakSource &sceneToCheck)
{
for (const auto &scene : scenes) {
if (scene == sceneToCheck) {
return true;
}
}
return false;
}
bool MacroConditionTransition::CheckCondition()
{
std::lock_guard<std::mutex> lock(_mutex);
bool ret = false;
switch (_condition) {
case Condition::CURRENT: {
auto transition = _transition.GetTransition();
ret = isCurrentTransition(transition);
break;
}
case Condition::DURATION:
ret = _duration.Milliseconds() ==
obs_frontend_get_transition_duration();
break;
case Condition::STARTED:
ret = _started;
break;
case Condition::ENDED:
ret = _ended;
break;
case Condition::VIDEO_ENDED:
ret = _videoEnded;
break;
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
_started = false;
_ended = false;
_videoEnded = false;
_transitionStartScenes.clear();
_transitionEndScenes.clear();
return ret;
}
void MacroConditionTransition::ConnectToTransitionSignals()
{
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::TransitionStarted(void *data, calldata_t *cd)
{
auto *condition = static_cast<MacroConditionTransition *>(data);
if (!condition) {
return;
}
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<std::mutex> 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 *condition = static_cast<MacroConditionTransition *>(data);
if (!condition) {
return;
}
std::lock_guard<std::mutex> lock(condition->_mutex);
condition->_ended = true;
}
void MacroConditionTransition::TransitionVideoEnded(void *data, calldata_t *)
{
auto *condition = static_cast<MacroConditionTransition *>(data);
if (!condition) {
return;
}
std::lock_guard<std::mutex> lock(condition->_mutex);
condition->_videoEnded = true;
}
void MacroConditionTransition::HandleFrontendEvent(obs_frontend_event event,
void *data)
{
auto condition = reinterpret_cast<MacroConditionTransition *>(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
{
MacroCondition::Save(obj);
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
_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<MacroConditionTransition::Condition>(
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<TransitionCondition>(
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 == Condition::CURRENT ||
_condition == Condition::DURATION ||
_condition == Condition::STARTED ||
_condition == Condition::ENDED) {
return _transition.ToString();
}
return "";
}
static inline void populateConditionSelection(QComboBox *list)
{
for (const auto &[value, name] : conditionTypes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
MacroConditionTransitionEdit::MacroConditionTransitionEdit(
QWidget *parent, std::shared_ptr<MacroConditionTransition> entryData)
: QWidget(parent)
{
_conditions = new QComboBox();
_transitions = new TransitionSelectionWidget(this, true, true);
_scenes = new SceneSelectionWidget(this, true, false, true, true);
_duration = new DurationSelection(this, false);
_durationSuffix = new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.transition.durationSuffix"));
populateConditionSelection(_conditions);
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_transitions,
SIGNAL(TransitionChanged(const TransitionSelection &)),
this,
SLOT(TransitionChanged(const TransitionSelection &)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
auto layout = new QHBoxLayout;
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.transition.entry"),
layout,
{{"{{conditions}}", _conditions},
{"{{transitions}}", _transitions},
{"{{scenes}}", _scenes},
{"{{duration}}", _duration},
{"{{durationSuffix}}", _durationSuffix}});
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionTransitionEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
{
auto lock = LockContext();
_entryData->_condition =
static_cast<MacroConditionTransition::Condition>(
_conditions->itemData(index).toInt());
}
SetWidgetVisibility();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionTransitionEdit::TransitionChanged(
const TransitionSelection &t)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_transition = t;
_entryData->ConnectToTransitionSignals();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionTransitionEdit::SceneChanged(const SceneSelection &s)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_scene = s;
}
void MacroConditionTransitionEdit::DurationChanged(const Duration &dur)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_duration = dur;
}
void MacroConditionTransitionEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
_transitions->setVisible(
_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 ==
MacroConditionTransition::Condition::DURATION);
_durationSuffix->setVisible(
_entryData->_condition ==
MacroConditionTransition::Condition::DURATION);
bool addCurrent = _entryData->_condition ==
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);
}
void MacroConditionTransitionEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
SetWidgetVisibility();
_conditions->setCurrentIndex(_conditions->findData(
static_cast<int>(_entryData->_condition)));
_transitions->SetTransition(_entryData->_transition);
_scenes->SetScene(_entryData->_scene);
_duration->SetDuration(_entryData->_duration);
}
} // namespace advss