From eafad7fe7f44cea3863016e4b492b691f50eba63 Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Sun, 28 Nov 2021 18:45:54 +0100 Subject: [PATCH] Add option to match any or all media sources of a specific scene --- data/locale/en-US.ini | 5 +- src/headers/macro-condition-media.hpp | 25 ++- src/macro-condition-media.cpp | 250 +++++++++++++++++++++++--- 3 files changed, 256 insertions(+), 24 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 675fc119..ff3db869 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -108,7 +108,10 @@ AdvSceneSwitcher.condition.file.entry.line1="Content of {{fileType}} {{filePath} AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}" AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}" AdvSceneSwitcher.condition.media="Media" -AdvSceneSwitcher.condition.media.entry="{{mediaSources}} state is {{states}} and {{timeRestrictions}} {{time}}" +AdvSceneSwitcher.condition.media.anyOnScene="Any media source on" +AdvSceneSwitcher.condition.media.allOnScene="All media sources on" +AdvSceneSwitcher.condition.media.matchOnChange="Only match on change (Note: This option will be removed in a future version - please use time constraints instead)" +AdvSceneSwitcher.condition.media.entry="{{mediaSources}}{{scenes}} state is {{states}} and {{timeRestrictions}} {{time}}" AdvSceneSwitcher.condition.video="Video" AdvSceneSwitcher.condition.video.condition.match="exactly matches" AdvSceneSwitcher.condition.video.condition.differ="does not match" diff --git a/src/headers/macro-condition-media.hpp b/src/headers/macro-condition-media.hpp index 9333ad01..c67d4e15 100644 --- a/src/headers/macro-condition-media.hpp +++ b/src/headers/macro-condition-media.hpp @@ -3,8 +3,10 @@ #include #include #include +#include #include "duration-control.hpp" +#include "scene-selection.hpp" enum class MediaTimeRestriction { TIME_RESTRICTION_NONE, @@ -33,6 +35,12 @@ enum class MediaState { ANY, }; +enum class MediaSourceType { + SOURCE, + ANY, + ALL, +}; + class MacroConditionMedia : public MacroCondition { public: ~MacroConditionMedia(); @@ -50,15 +58,23 @@ public: static void MediaStopped(void *data, calldata_t *); static void MediaEnded(void *data, calldata_t *); + MediaSourceType _sourceType = MediaSourceType::SOURCE; + SceneSelection _scene; OBSWeakSource _source = nullptr; + std::vector _sources; MediaState _state = MediaState::OBS_MEDIA_STATE_NONE; MediaTimeRestriction _restriction = MediaTimeRestriction::TIME_RESTRICTION_NONE; Duration _time; + bool _onlyMatchonChagne = false; private: - std::atomic_bool _stopped = {false}; - std::atomic_bool _ended = {false}; + bool CheckMediaMatch(); + + bool _stopped = false; + bool _ended = false; + // TODO: Remove _alreadyMatched as it does not make much sense when + // time restrictions for macro conditions are available. // Trigger scene change only once even if media state might trigger repeatedly bool _alreadyMatched = false; // Workaround to enable use of "ended" to specify end of VLC playlist @@ -87,20 +103,25 @@ public: private slots: void SourceChanged(const QString &text); + void SceneChanged(const SceneSelection &); void StateChanged(int index); void TimeRestrictionChanged(int index); void TimeChanged(double seconds); void TimeUnitChanged(DurationUnit unit); + void OnChangeChanged(int); signals: void HeaderInfoChanged(const QString &); protected: + SceneSelectionWidget *_scenes; QComboBox *_mediaSources; QComboBox *_states; QComboBox *_timeRestrictions; DurationSelection *_time; + QCheckBox *_onChange; std::shared_ptr _entryData; private: + void SetWidgetVisibility(); bool _loading = true; }; diff --git a/src/macro-condition-media.cpp b/src/macro-condition-media.cpp index 6089668d..208359d0 100644 --- a/src/macro-condition-media.cpp +++ b/src/macro-condition-media.cpp @@ -80,18 +80,19 @@ bool matchTime(const int64_t currentTime, const int64_t duration, matchedTimeRemainLonger || matchedTimeRemainShorter; } -bool MacroConditionMedia::CheckCondition() +bool MacroConditionMedia::CheckMediaMatch() { + if (!_source) { return false; } bool match = false; - obs_source_t *source = obs_weak_source_get_source(_source); - auto duration = obs_source_media_get_duration(source); - auto time = obs_source_media_get_time(source); - obs_media_state currentState = obs_source_media_get_state(source); - obs_source_release(source); + obs_source_t *s = obs_weak_source_get_source(_source); + auto duration = obs_source_media_get_duration(s); + auto time = obs_source_media_get_time(s); + obs_media_state currentState = obs_source_media_get_state(s); + obs_source_release(s); // To be able to compare to obs_media_state more easily int expectedState = static_cast(_state); @@ -150,49 +151,146 @@ bool MacroConditionMedia::CheckCondition() matchTime(time, duration, _restriction, _time.seconds * 1000); bool matched = matchedState && matchedTime; - if (matched && !_alreadyMatched) { + if (matched && !(_onlyMatchonChagne && _alreadyMatched)) { match = true; } - _alreadyMatched = matched; return match; } +bool MacroConditionMedia::CheckCondition() +{ + bool match = false; + switch (_sourceType) { + case MediaSourceType::ANY: + for (auto &source : _sources) { + match = match || source.CheckCondition(); + } + break; + case MediaSourceType::ALL: { + bool res = true; + for (auto &source : _sources) { + res = res && source.CheckCondition(); + } + match = res; + break; + } + case MediaSourceType::SOURCE: + match = CheckMediaMatch(); + break; + default: + break; + } + return match; +} + bool MacroConditionMedia::Save(obs_data_t *obj) { MacroCondition::Save(obj); obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str()); - + _scene.Save(obj); + obs_data_set_int(obj, "sourceType", static_cast(_sourceType)); obs_data_set_int(obj, "state", static_cast(_state)); obs_data_set_int(obj, "restriction", static_cast(_restriction)); _time.Save(obj); + obs_data_set_bool(obj, "matchOnChagne", _onlyMatchonChagne); return true; } +static bool enumSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *ptr) +{ + auto *sources = reinterpret_cast *>(ptr); + + if (obs_sceneitem_is_group(item)) { + obs_scene_t *scene = obs_sceneitem_group_get_scene(item); + obs_scene_enum_items(scene, enumSceneItem, ptr); + } + + auto source = obs_sceneitem_get_source(item); + std::string sourceId = obs_source_get_id(source); + + if (sourceId.compare("ffmpeg_source") == 0 || + sourceId.compare("vlc_source") == 0) { + auto ws = obs_source_get_weak_source(source); + obs_weak_source_release(ws); + sources->emplace_back(ws); + } + return true; +} + +void forMediaSourceOnSceneAddMediaCondition( + OBSWeakSource sceneWeakSource, MacroConditionMedia *origCond, + std::vector &conditions) +{ + conditions.clear(); + std::vector mediaSources; + auto s = obs_weak_source_get_source(sceneWeakSource); + auto scene = obs_scene_from_source(s); + obs_scene_enum_items(scene, enumSceneItem, &mediaSources); + obs_source_release(s); + + for (auto &source : mediaSources) { + MacroConditionMedia cond(*origCond); + cond._sourceType = MediaSourceType::SOURCE; + cond._source = source; + conditions.push_back(cond); + } +} + bool MacroConditionMedia::Load(obs_data_t *obj) { MacroCondition::Load(obj); const char *sourceName = obs_data_get_string(obj, "source"); _source = GetWeakSourceByName(sourceName); - + _scene.Load(obj); + _sourceType = static_cast( + obs_data_get_int(obj, "sourceType")); _state = static_cast(obs_data_get_int(obj, "state")); _restriction = static_cast( obs_data_get_int(obj, "restriction")); _time.Load(obj); + if (!obs_data_has_user_value(obj, "matchOnChagne")) { + _onlyMatchonChagne = true; + } else { + _onlyMatchonChagne = obs_data_get_bool(obj, "matchOnChagne"); + } - obs_source_t *mediasource = obs_weak_source_get_source(_source); - signal_handler_t *sh = obs_source_get_signal_handler(mediasource); - signal_handler_connect(sh, "media_stopped", MediaStopped, this); - signal_handler_connect(sh, "media_ended", MediaEnded, this); - obs_source_release(mediasource); + if (_sourceType == MediaSourceType::SOURCE) { + obs_source_t *mediasource = obs_weak_source_get_source(_source); + signal_handler_t *sh = + obs_source_get_signal_handler(mediasource); + signal_handler_connect(sh, "media_stopped", MediaStopped, this); + signal_handler_connect(sh, "media_ended", MediaEnded, this); + obs_source_release(mediasource); + } + + forMediaSourceOnSceneAddMediaCondition(_scene.GetScene(), this, + _sources); return true; } std::string MacroConditionMedia::GetShortDesc() { - if (_source) { - return GetWeakSourceName(_source); + switch (_sourceType) { + case MediaSourceType::SOURCE: + if (_source) { + return GetWeakSourceName(_source); + } + case MediaSourceType::ANY: + if (_scene.GetScene()) { + return obs_module_text( + "AdvSceneSwitcher.condition.media.anyOnScene") + + std::string(" ") + _scene.ToString(); + } + case MediaSourceType::ALL: + if (_scene.GetScene()) { + return obs_module_text( + "AdvSceneSwitcher.condition.media.allOnScene") + + std::string(" ") + _scene.ToString(); + } + default: + break; } return ""; } @@ -243,18 +341,33 @@ static void populateMediaStates(QComboBox *list) } } +static void addAnyAndAllStates(QComboBox *list) +{ + list->insertItem( + 1, + obs_module_text("AdvSceneSwitcher.condition.media.anyOnScene")); + list->insertItem( + 1, + obs_module_text("AdvSceneSwitcher.condition.media.allOnScene")); +} + MacroConditionMediaEdit::MacroConditionMediaEdit( QWidget *parent, std::shared_ptr entryData) : QWidget(parent) { _mediaSources = new QComboBox(); + _scenes = new SceneSelectionWidget(window()); _states = new QComboBox(); _timeRestrictions = new QComboBox(); _time = new DurationSelection(); + _onChange = new QCheckBox(obs_module_text( + "AdvSceneSwitcher.condition.media.matchOnChange")); QWidget::connect(_mediaSources, SIGNAL(currentTextChanged(const QString &)), this, SLOT(SourceChanged(const QString &))); + QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)), + this, SLOT(SceneChanged(const SceneSelection &))); QWidget::connect(_states, SIGNAL(currentIndexChanged(int)), this, SLOT(StateChanged(int))); QWidget::connect(_timeRestrictions, SIGNAL(currentIndexChanged(int)), @@ -263,20 +376,27 @@ MacroConditionMediaEdit::MacroConditionMediaEdit( SLOT(TimeChanged(double))); QWidget::connect(_time, SIGNAL(UnitChanged(DurationUnit)), this, SLOT(TimeUnitChanged(DurationUnit))); + QWidget::connect(_onChange, SIGNAL(stateChanged(int)), this, + SLOT(OnChangeChanged(int))); populateMediaSelection(_mediaSources); + addAnyAndAllStates(_mediaSources); populateMediaStates(_states); populateMediaTimeRestrictions(_timeRestrictions); - QHBoxLayout *mainLayout = new QHBoxLayout; + QHBoxLayout *entryLayout = new QHBoxLayout; std::unordered_map widgetPlaceholders = { {"{{mediaSources}}", _mediaSources}, + {"{{scenes}}", _scenes}, {"{{states}}", _states}, {"{{timeRestrictions}}", _timeRestrictions}, {"{{time}}", _time}, }; placeWidgets(obs_module_text("AdvSceneSwitcher.condition.media.entry"), - mainLayout, widgetPlaceholders); + entryLayout, widgetPlaceholders); + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(entryLayout); + mainLayout->addWidget(_onChange); setLayout(mainLayout); _entryData = entryData; @@ -291,11 +411,41 @@ void MacroConditionMediaEdit::SourceChanged(const QString &text) } std::lock_guard lock(switcher->m); + + if (text == + obs_module_text("AdvSceneSwitcher.condition.media.anyOnScene")) { + _entryData->_sourceType = MediaSourceType::ANY; + } else if (text == + obs_module_text( + "AdvSceneSwitcher.condition.media.allOnScene")) { + _entryData->_sourceType = MediaSourceType::ALL; + } else { + _entryData->_sources.clear(); + _entryData->_sourceType = MediaSourceType::SOURCE; + } + _entryData->ClearSignalHandler(); _entryData->_source = GetWeakSourceByQString(text); _entryData->ResetSignalHandler(); emit HeaderInfoChanged( QString::fromStdString(_entryData->GetShortDesc())); + + SetWidgetVisibility(); +} + +void MacroConditionMediaEdit::SceneChanged(const SceneSelection &s) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_scene = s; + forMediaSourceOnSceneAddMediaCondition(_entryData->_scene.GetScene(), + _entryData.get(), + _entryData->_sources); + emit HeaderInfoChanged( + QString::fromStdString(_entryData->GetShortDesc())); } MediaState getMediaStateFromIdx(int idx) @@ -318,6 +468,11 @@ void MacroConditionMediaEdit::StateChanged(int index) std::lock_guard lock(switcher->m); _entryData->_state = getMediaStateFromIdx(index); + if (_entryData->_sourceType != MediaSourceType::SOURCE) { + forMediaSourceOnSceneAddMediaCondition( + _entryData->_scene.GetScene(), _entryData.get(), + _entryData->_sources); + } } void MacroConditionMediaEdit::TimeRestrictionChanged(int index) @@ -335,6 +490,11 @@ void MacroConditionMediaEdit::TimeRestrictionChanged(int index) std::lock_guard lock(switcher->m); _entryData->_restriction = static_cast(index); + if (_entryData->_sourceType != MediaSourceType::SOURCE) { + forMediaSourceOnSceneAddMediaCondition( + _entryData->_scene.GetScene(), _entryData.get(), + _entryData->_sources); + } } void MacroConditionMediaEdit::TimeChanged(double seconds) @@ -345,6 +505,11 @@ void MacroConditionMediaEdit::TimeChanged(double seconds) std::lock_guard lock(switcher->m); _entryData->_time.seconds = seconds; + if (_entryData->_sourceType != MediaSourceType::SOURCE) { + forMediaSourceOnSceneAddMediaCondition( + _entryData->_scene.GetScene(), _entryData.get(), + _entryData->_sources); + } } void MacroConditionMediaEdit::TimeUnitChanged(DurationUnit unit) @@ -355,6 +520,31 @@ void MacroConditionMediaEdit::TimeUnitChanged(DurationUnit unit) std::lock_guard lock(switcher->m); _entryData->_time.displayUnit = unit; + if (_entryData->_sourceType != MediaSourceType::SOURCE) { + forMediaSourceOnSceneAddMediaCondition( + _entryData->_scene.GetScene(), _entryData.get(), + _entryData->_sources); + } +} + +void MacroConditionMediaEdit::OnChangeChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_onlyMatchonChagne = value; + if (_entryData->_sourceType != MediaSourceType::SOURCE) { + forMediaSourceOnSceneAddMediaCondition( + _entryData->_scene.GetScene(), _entryData.get(), + _entryData->_sources); + } +} + +void MacroConditionMediaEdit::SetWidgetVisibility() +{ + _scenes->setVisible(_entryData->_sourceType != MediaSourceType::SOURCE); } int getIdxFromMediaState(MediaState state) @@ -373,8 +563,24 @@ void MacroConditionMediaEdit::UpdateEntryData() return; } - _mediaSources->setCurrentText( - GetWeakSourceName(_entryData->_source).c_str()); + switch (_entryData->_sourceType) { + case MediaSourceType::ANY: + _mediaSources->setCurrentText(obs_module_text( + "AdvSceneSwitcher.condition.media.anyOnScene")); + break; + case MediaSourceType::ALL: + _mediaSources->setCurrentText(obs_module_text( + "AdvSceneSwitcher.condition.media.allOnScene")); + break; + case MediaSourceType::SOURCE: + _mediaSources->setCurrentText( + GetWeakSourceName(_entryData->_source).c_str()); + break; + default: + break; + } + + _scenes->SetScene(_entryData->_scene); _states->setCurrentIndex(getIdxFromMediaState(_entryData->_state)); _timeRestrictions->setCurrentIndex( static_cast(_entryData->_restriction)); @@ -383,4 +589,6 @@ void MacroConditionMediaEdit::UpdateEntryData() MediaTimeRestriction::TIME_RESTRICTION_NONE) { _time->setDisabled(true); } + _onChange->setChecked(_entryData->_onlyMatchonChagne); + SetWidgetVisibility(); }