#include "macro-action-scene-switch.hpp" #include "layout-helpers.hpp" #include "macro-helpers.hpp" #include "plugin-state-helpers.hpp" #include "scene-switch-helpers.hpp" #include "source-helpers.hpp" #include namespace advss { using namespace std::chrono_literals; const std::string MacroActionSwitchScene::id = MacroAction::GetDefaultID().data(); bool MacroActionSwitchScene::_registered = MacroActionFactory::Register( MacroActionSwitchScene::id, {MacroActionSwitchScene::Create, MacroActionSwitchSceneEdit::Create, "AdvSceneSwitcher.action.scene"}); const static std::map sceneTypes = { {MacroActionSwitchScene::SceneType::PROGRAM, "AdvSceneSwitcher.action.scene.type.program"}, {MacroActionSwitchScene::SceneType::PREVIEW, "AdvSceneSwitcher.action.scene.type.preview"}, }; static void waitForTransitionChange(OBSWeakSource &transition, std::unique_lock *lock, Macro *macro) { const auto time = 100ms; obs_source_t *source = obs_weak_source_get_source(transition); if (!source) { return; } bool stillTransitioning = true; while (stillTransitioning && !MacroWaitShouldAbort() && !MacroIsStopped(macro)) { GetMacroTransitionCV().wait_for(*lock, time); float t = obs_transition_get_time(source); stillTransitioning = t < 1.0f && t > 0.0f; } obs_source_release(source); } static void waitForTransitionChangeFixedDuration( int duration, std::unique_lock *lock, Macro *macro) { duration += 200; // It seems to be necessary to add a small buffer auto time = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(duration); while (!MacroWaitShouldAbort() && !MacroIsStopped(macro)) { if (GetMacroTransitionCV().wait_until(*lock, time) == std::cv_status::timeout) { break; } } } static int getTransitionOverrideDuration(OBSWeakSource &scene) { int duration = 0; obs_source_t *source = obs_weak_source_get_source(scene); obs_data_t *data = obs_source_get_private_settings(source); auto name = obs_data_get_string(data, "transition"); if (strlen(name) != 0) { duration = obs_data_get_int(data, "transition_duration"); } obs_data_release(data); obs_source_release(source); return duration; } static bool isUsingFixedLengthTransition(const OBSWeakSource &transition) { obs_source_t *source = obs_weak_source_get_source(transition); bool ret = obs_transition_fixed(source); obs_source_release(source); return ret; } static OBSWeakSource getOverrideTransition(OBSWeakSource &scene) { OBSWeakSource transition; obs_source_t *source = obs_weak_source_get_source(scene); obs_data_t *data = obs_source_get_private_settings(source); transition = GetWeakTransitionByName( obs_data_get_string(data, "transition")); obs_data_release(data); obs_source_release(source); return transition; } static int getExpectedTransitionDuration(OBSWeakSource &scene, OBSWeakSource &transiton_, double duration) { OBSWeakSource transition = transiton_; // If we are not modifying the transition override, we will have to // check, if a transition override is set by the user and use its // duration instead if (!ShouldModifyTransitionOverrides()) { auto overrideTransition = getOverrideTransition(scene); if (overrideTransition) { transition = overrideTransition; if (!isUsingFixedLengthTransition(transition)) { return getTransitionOverrideDuration(scene); } } } if (isUsingFixedLengthTransition(transition)) { return -1; // no API is available to access the fixed duration } if (duration != 0) { return duration * 1000; } return obs_frontend_get_transition_duration(); } bool MacroActionSwitchScene::WaitForTransition(OBSWeakSource &scene, OBSWeakSource &transition) { const int expectedTransitionDuration = getExpectedTransitionDuration( scene, transition, _duration.Seconds()); SetMacroAbortWait(false); std::unique_lock lock(*GetMutex()); if (expectedTransitionDuration < 0) { waitForTransitionChange(transition, &lock, GetMacro()); } else { waitForTransitionChangeFixedDuration(expectedTransitionDuration, &lock, GetMacro()); } return !MacroWaitShouldAbort(); } bool MacroActionSwitchScene::PerformAction() { auto scene = _scene.GetScene(); if (_sceneType == SceneType::PREVIEW) { OBSSourceAutoRelease previewScneSource = obs_weak_source_get_source(scene); obs_frontend_set_current_preview_scene(previewScneSource); return true; } auto transition = _transition.GetTransition(); SwitchScene({scene, transition, (int)(_duration.Milliseconds())}, obs_frontend_preview_program_mode_active()); if (_blockUntilTransitionDone && scene && scene != GetCurrentScene()) { return WaitForTransition(scene, transition); } return true; } void MacroActionSwitchScene::LogAction() const { ablog(LOG_INFO, "switch%s scene to '%s'", _sceneType == SceneType::PREVIEW ? " preview" : "", _scene.ToString(true).c_str()); } bool MacroActionSwitchScene::Save(obs_data_t *obj) const { MacroAction::Save(obj); _scene.Save(obj); _transition.Save(obj); _duration.Save(obj); obs_data_set_bool(obj, "blockUntilTransitionDone", _blockUntilTransitionDone); obs_data_set_int(obj, "sceneType", static_cast(_sceneType)); return true; } bool MacroActionSwitchScene::Load(obs_data_t *obj) { MacroAction::Load(obj); _scene.Load(obj); _transition.Load(obj); _duration.Load(obj); _blockUntilTransitionDone = obs_data_get_bool(obj, "blockUntilTransitionDone"); _sceneType = static_cast(obs_data_get_int(obj, "sceneType")); return true; } std::string MacroActionSwitchScene::GetShortDesc() const { return _scene.ToString(); } std::shared_ptr MacroActionSwitchScene::Create(Macro *m) { return std::make_shared(m); } std::shared_ptr MacroActionSwitchScene::Copy() const { return std::make_shared(*this); } void MacroActionSwitchScene::ResolveVariablesToFixedValues() { _scene.ResolveVariables(); _duration.ResolveVariables(); } static inline void populateTypeSelection(QComboBox *list) { for (const auto &[_, name] : sceneTypes) { list->addItem(obs_module_text(name.c_str())); } } MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit( QWidget *parent, std::shared_ptr entryData) : QWidget(parent), _scenes(new SceneSelectionWidget(this, true, true, true)), _transitions(new TransitionSelectionWidget(this)), _duration(new DurationSelection(parent, false)), _blockUntilTransitionDone(new QCheckBox(obs_module_text( "AdvSceneSwitcher.action.scene.blockUntilTransitionDone"))), _sceneTypes(new QComboBox()), _entryLayout(new QHBoxLayout()) { _duration->SpinBox()->setSpecialValueText("-"); populateTypeSelection(_sceneTypes); QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)), this, SLOT(SceneChanged(const SceneSelection &))); QWidget::connect(_transitions, SIGNAL(TransitionChanged(const TransitionSelection &)), this, SLOT(TransitionChanged(const TransitionSelection &))); QWidget::connect(_duration, SIGNAL(DurationChanged(const Duration &)), this, SLOT(DurationChanged(const Duration &))); QWidget::connect(_blockUntilTransitionDone, SIGNAL(stateChanged(int)), this, SLOT(BlockUntilTransitionDoneChanged(int))); QWidget::connect(_sceneTypes, SIGNAL(currentIndexChanged(int)), this, SLOT(SceneTypeChanged(int))); PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.scene.entry"), _entryLayout, {{"{{scenes}}", _scenes}, {"{{transitions}}", _transitions}, {"{{duration}}", _duration}, {"{{sceneTypes}}", _sceneTypes}}); auto mainLayout = new QVBoxLayout; mainLayout->addLayout(_entryLayout); mainLayout->addWidget(_blockUntilTransitionDone); setLayout(mainLayout); _entryData = entryData; _sceneTypes->setCurrentIndex(static_cast(_entryData->_sceneType)); _scenes->SetScene(_entryData->_scene); _transitions->SetTransition(_entryData->_transition); _duration->SetDuration(_entryData->_duration); _blockUntilTransitionDone->setChecked( _entryData->_blockUntilTransitionDone); SetWidgetVisibility(); _loading = false; } void MacroActionSwitchSceneEdit::DurationChanged(const Duration &dur) { GUARD_LOADING_AND_LOCK(); _entryData->_duration = dur; } void MacroActionSwitchSceneEdit::BlockUntilTransitionDoneChanged(int state) { GUARD_LOADING_AND_LOCK(); _entryData->_blockUntilTransitionDone = state; } void MacroActionSwitchSceneEdit::SceneTypeChanged(int value) { GUARD_LOADING_AND_LOCK(); _entryData->_sceneType = static_cast(value); SetWidgetVisibility(); } void MacroActionSwitchSceneEdit::SetWidgetVisibility() { _entryLayout->removeWidget(_scenes); _entryLayout->removeWidget(_transitions); _entryLayout->removeWidget(_duration); _entryLayout->removeWidget(_sceneTypes); ClearLayout(_entryLayout); std::unordered_map widgetPlaceholders = { {"{{scenes}}", _scenes}, {"{{transitions}}", _transitions}, {"{{duration}}", _duration}, {"{{sceneTypes}}", _sceneTypes}, }; if (_entryData->_sceneType == MacroActionSwitchScene::SceneType::PREVIEW) { _transitions->hide(); _duration->hide(); PlaceWidgets( obs_module_text( "AdvSceneSwitcher.action.scene.entry.preview"), _entryLayout, widgetPlaceholders); return; } _transitions->show(); if (_entryData->_transition.GetType() != TransitionSelection::Type::TRANSITION) { _duration->show(); } const bool fixedDuration = isUsingFixedLengthTransition( _entryData->_transition.GetTransition()); _duration->setVisible(!fixedDuration); if (fixedDuration) { PlaceWidgets( obs_module_text( "AdvSceneSwitcher.action.scene.entry.noDuration"), _entryLayout, widgetPlaceholders); } else { PlaceWidgets( obs_module_text("AdvSceneSwitcher.action.scene.entry"), _entryLayout, widgetPlaceholders); } } void MacroActionSwitchSceneEdit::SceneChanged(const SceneSelection &s) { GUARD_LOADING_AND_LOCK(); _entryData->_scene = s; emit HeaderInfoChanged( QString::fromStdString(_entryData->GetShortDesc())); } void MacroActionSwitchSceneEdit::TransitionChanged(const TransitionSelection &t) { GUARD_LOADING_AND_LOCK(); _entryData->_transition = t; SetWidgetVisibility(); } } // namespace advss