#include "macro-condition-media.hpp" #include "layout-helpers.hpp" #include "macro-helpers.hpp" #include "plugin-state-helpers.hpp" #include "scene-switch-helpers.hpp" #include "selection-helpers.hpp" #include "source-helpers.hpp" namespace advss { const std::string MacroConditionMedia::id = "media"; bool MacroConditionMedia::_registered = MacroConditionFactory::Register( MacroConditionMedia::id, {MacroConditionMedia::Create, MacroConditionMediaEdit::Create, "AdvSceneSwitcher.condition.media"}); static const std::map mediaTimeRestrictions = { {MacroConditionMedia::Time::TIME_RESTRICTION_NONE, "AdvSceneSwitcher.mediaTab.timeRestriction.none"}, {MacroConditionMedia::Time::TIME_RESTRICTION_SHORTER, "AdvSceneSwitcher.mediaTab.timeRestriction.shorter"}, {MacroConditionMedia::Time::TIME_RESTRICTION_LONGER, "AdvSceneSwitcher.mediaTab.timeRestriction.longer"}, {MacroConditionMedia::Time::TIME_RESTRICTION_REMAINING_SHORTER, "AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter"}, {MacroConditionMedia::Time::TIME_RESTRICTION_REMAINING_LONGER, "AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger"}, }; static const std::map mediaStates = { {MacroConditionMedia::State::OBS_MEDIA_STATE_NONE, "AdvSceneSwitcher.mediaTab.states.none"}, {MacroConditionMedia::State::OBS_MEDIA_STATE_PLAYING, "AdvSceneSwitcher.mediaTab.states.playing"}, {MacroConditionMedia::State::OBS_MEDIA_STATE_OPENING, "AdvSceneSwitcher.mediaTab.states.opening"}, {MacroConditionMedia::State::OBS_MEDIA_STATE_BUFFERING, "AdvSceneSwitcher.mediaTab.states.buffering"}, {MacroConditionMedia::State::OBS_MEDIA_STATE_PAUSED, "AdvSceneSwitcher.mediaTab.states.paused"}, {MacroConditionMedia::State::OBS_MEDIA_STATE_STOPPED, "AdvSceneSwitcher.mediaTab.states.stopped"}, {MacroConditionMedia::State::OBS_MEDIA_STATE_ENDED, "AdvSceneSwitcher.mediaTab.states.ended"}, {MacroConditionMedia::State::OBS_MEDIA_STATE_ERROR, "AdvSceneSwitcher.mediaTab.states.error"}, {MacroConditionMedia::State::PLAYLIST_ENDED, "AdvSceneSwitcher.mediaTab.states.playlistEnd"}, {MacroConditionMedia::State::ANY, "AdvSceneSwitcher.mediaTab.states.any"}, }; bool MacroConditionMedia::CheckTime() { auto s = OBSGetStrongRef(_source.GetSource()); auto duration = obs_source_media_get_duration(s); auto currentTime = obs_source_media_get_time(s); bool match = false; switch (_timeRestriction) { case Time::TIME_RESTRICTION_NONE: match = true; break; case Time::TIME_RESTRICTION_SHORTER: match = currentTime < _time.Milliseconds(); break; case Time::TIME_RESTRICTION_LONGER: match = currentTime > _time.Milliseconds(); break; case Time::TIME_RESTRICTION_REMAINING_SHORTER: match = duration > currentTime && duration - currentTime < _time.Milliseconds(); break; case Time::TIME_RESTRICTION_REMAINING_LONGER: match = duration > currentTime && duration - currentTime > _time.Milliseconds(); break; default: break; } SetTempVarValues(s, MediaTimeInfo{duration, currentTime}); return match; } bool MacroConditionMedia::CheckState() { auto s = OBSGetStrongRef(_source.GetSource()); obs_media_state currentState = obs_source_media_get_state(s); bool match = false; // To be able to compare to obs_media_state more easily int expectedState = static_cast(_state); switch (_state) { case State::OBS_MEDIA_STATE_STOPPED: match = _stopped || currentState == OBS_MEDIA_STATE_STOPPED; break; case State::OBS_MEDIA_STATE_ENDED: match = _ended || currentState == OBS_MEDIA_STATE_ENDED; break; case State::PLAYLIST_ENDED: match = CheckPlaylistEnd(currentState); break; case State::ANY: match = true; break; case State::OBS_MEDIA_STATE_NONE: case State::OBS_MEDIA_STATE_PLAYING: case State::OBS_MEDIA_STATE_OPENING: case State::OBS_MEDIA_STATE_BUFFERING: case State::OBS_MEDIA_STATE_PAUSED: case State::OBS_MEDIA_STATE_ERROR: match = currentState == expectedState; break; default: break; } SetTempVarValues(s, currentState); return match; } bool MacroConditionMedia::CheckPlaylistEnd(const obs_media_state currentState) { bool consecutiveEndedStates = false; if (_next || currentState != OBS_MEDIA_STATE_ENDED) { _previousStateEnded = false; } if (currentState == OBS_MEDIA_STATE_ENDED && _previousStateEnded) { consecutiveEndedStates = true; } _previousStateEnded = _ended || currentState == OBS_MEDIA_STATE_ENDED; return consecutiveEndedStates; } bool MacroConditionMedia::CheckMediaMatch() { if (!_source.GetSource()) { return false; } bool matched = false; switch (_checkType) { case CheckType::STATE: matched = CheckState(); break; case CheckType::TIME: matched = CheckTime(); break; case CheckType::LEGACY: matched = CheckState() && CheckTime(); break; default: break; } // reset for next check _stopped = false; _ended = false; _next = false; return matched; } void MacroConditionMedia::HandleSceneChange() { UpdateMediaSourcesOfSceneList(); _lastConfigureScene = GetCurrentScene(); } static bool isVLCSource(obs_source_t *source) { return source && strcmp(obs_source_get_unversioned_id(source), "vlc_source") == 0; } void MacroConditionMedia::SetupTempVars() { MacroCondition::SetupTempVars(); if (_sourceType != SourceType::SOURCE) { return; } if (_checkType == CheckType::STATE) { AddTempvar( "state", obs_module_text("AdvSceneSwitcher.tempVar.media.state"), obs_module_text( "AdvSceneSwitcher.tempVar.media.state.description")); } else if (_checkType == CheckType::TIME) { AddTempvar( "time", obs_module_text("AdvSceneSwitcher.tempVar.media.time"), obs_module_text( "AdvSceneSwitcher.tempVar.media.time.description")); AddTempvar( "duration", obs_module_text( "AdvSceneSwitcher.tempVar.media.duration"), obs_module_text( "AdvSceneSwitcher.tempVar.media.duration.description")); } auto source = OBSGetStrongRef(_source.GetSource()); if (!isVLCSource(source)) { return; } #define CREATE_VLC_TEMPVAR(name) \ AddTempvar( \ name, \ obs_module_text("AdvSceneSwitcher.tempVar.media.vlc." name), \ obs_module_text( \ "AdvSceneSwitcher.tempVar.media.vlc.metadata.description")); CREATE_VLC_TEMPVAR("title") CREATE_VLC_TEMPVAR("artist") CREATE_VLC_TEMPVAR("genre") CREATE_VLC_TEMPVAR("copyright") CREATE_VLC_TEMPVAR("album") CREATE_VLC_TEMPVAR("track_number") CREATE_VLC_TEMPVAR("description") CREATE_VLC_TEMPVAR("rating") CREATE_VLC_TEMPVAR("date") CREATE_VLC_TEMPVAR("setting") CREATE_VLC_TEMPVAR("url") CREATE_VLC_TEMPVAR("language") CREATE_VLC_TEMPVAR("now_playing") CREATE_VLC_TEMPVAR("publisher") CREATE_VLC_TEMPVAR("encoded_by") CREATE_VLC_TEMPVAR("artwork_url") CREATE_VLC_TEMPVAR("track_id") CREATE_VLC_TEMPVAR("director") CREATE_VLC_TEMPVAR("season") CREATE_VLC_TEMPVAR("episode") CREATE_VLC_TEMPVAR("show_name") CREATE_VLC_TEMPVAR("actors") CREATE_VLC_TEMPVAR("album_artist") CREATE_VLC_TEMPVAR("disc_number") CREATE_VLC_TEMPVAR("disc_total") #undef CREATE_TEMPVAR } void MacroConditionMedia::SetTempVarValues( obs_source_t *source, std::variant value) { if (std::holds_alternative(value)) { const auto state = std::get(value); SetTempVarValue("state", std::to_string(state)); } else { const auto timeInfo = std::get(value); SetTempVarValue("time", std::to_string(timeInfo.time)); SetTempVarValue("duration", std::to_string(timeInfo.duration)); } if (!isVLCSource(source)) { return; } SetVLCTempVarValueHelper(source, "title"); SetVLCTempVarValueHelper(source, "artist"); SetVLCTempVarValueHelper(source, "genre"); SetVLCTempVarValueHelper(source, "copyright"); SetVLCTempVarValueHelper(source, "album"); SetVLCTempVarValueHelper(source, "track_number"); SetVLCTempVarValueHelper(source, "description"); SetVLCTempVarValueHelper(source, "rating"); SetVLCTempVarValueHelper(source, "date"); SetVLCTempVarValueHelper(source, "setting"); SetVLCTempVarValueHelper(source, "url"); SetVLCTempVarValueHelper(source, "language"); SetVLCTempVarValueHelper(source, "now_playing"); SetVLCTempVarValueHelper(source, "publisher"); SetVLCTempVarValueHelper(source, "encoded_by"); SetVLCTempVarValueHelper(source, "artwork_url"); SetVLCTempVarValueHelper(source, "track_id"); SetVLCTempVarValueHelper(source, "director"); SetVLCTempVarValueHelper(source, "season"); SetVLCTempVarValueHelper(source, "episode"); SetVLCTempVarValueHelper(source, "show_name"); SetVLCTempVarValueHelper(source, "actors"); SetVLCTempVarValueHelper(source, "album_artist"); SetVLCTempVarValueHelper(source, "disc_number"); SetVLCTempVarValueHelper(source, "disc_total"); } void MacroConditionMedia::SetVLCTempVarValueHelper(obs_source_t *source, const char *id) { auto ph = obs_source_get_proc_handler(source); auto cd = calldata_create(); calldata_set_string(cd, "tag_id", id); if (!proc_handler_call(ph, "get_metadata", cd)) { SetTempVarValue(id, ""); calldata_destroy(cd); return; } const char *value; if (!calldata_get_string(cd, "tag_data", &value) || !value) { SetTempVarValue(id, ""); calldata_destroy(cd); return; } SetTempVarValue(id, value); calldata_destroy(cd); } MacroConditionMedia::MacroConditionMedia(const MacroConditionMedia &other) : MacroCondition(other.GetMacro()), _sourceType(other._sourceType), _checkType(other._checkType), _state(other._state), _timeRestriction(other._timeRestriction), _scene(other._scene), _source(other._source), _sourceGroup(), _time(other._time), _lastConfigureScene(other._lastConfigureScene) { ResetSignalHandler(); } MacroConditionMedia & MacroConditionMedia::operator=(const MacroConditionMedia &other) { _sourceType = other._sourceType; _checkType = other._checkType; _state = other._state; _timeRestriction = other._timeRestriction; _scene = other._scene; _source = other._source; _time = other._time; _lastConfigureScene = other._lastConfigureScene; ResetSignalHandler(); return *this; } bool MacroConditionMedia::CheckCondition() { bool match = false; switch (_sourceType) { case SourceType::ANY: for (auto &source : _sourceGroup) { match = match || source.CheckCondition(); } break; case SourceType::ALL: { bool res = true; for (auto &source : _sourceGroup) { res = res && source.CheckCondition(); } match = res; break; } case SourceType::SOURCE: match = CheckMediaMatch(); break; default: break; } if (_lastConfigureScene != GetCurrentScene()) { HandleSceneChange(); } return match; } bool MacroConditionMedia::Save(obs_data_t *obj) const { MacroCondition::Save(obj); _source.Save(obj); _scene.Save(obj); obs_data_set_int(obj, "sourceType", static_cast(_sourceType)); obs_data_set_int(obj, "checkType", static_cast(_checkType)); obs_data_set_int(obj, "state", static_cast(_state)); obs_data_set_int(obj, "restriction", static_cast(_timeRestriction)); _time.Save(obj); obs_data_set_int(obj, "version", 1); 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); if (IsMediaSource(source)) { OBSWeakSourceAutoRelease weak = obs_source_get_weak_source(source); sources->emplace_back(weak); } return true; } void MacroConditionMedia::UpdateMediaSourcesOfSceneList() { _sourceGroup.clear(); if (!_scene.GetScene(false)) { return; } std::vector mediaSources; OBSSourceAutoRelease s = obs_weak_source_get_source(_scene.GetScene(false)); auto scene = obs_scene_from_source(s); obs_scene_enum_items(scene, enumSceneItem, &mediaSources); _sourceGroup.reserve(mediaSources.size()); for (auto &source : mediaSources) { MacroConditionMedia cond(*this); cond._sourceType = SourceType::SOURCE; cond._source.SetSource(source); _sourceGroup.push_back(cond); } } void MacroConditionMedia::SetSource(const SourceSelection &source) { _source = source; SetupTempVars(); } bool MacroConditionMedia::Load(obs_data_t *obj) { MacroCondition::Load(obj); _source.Load(obj); _scene.Load(obj); _sourceType = static_cast(obs_data_get_int(obj, "sourceType")); _state = static_cast(obs_data_get_int(obj, "state")); _checkType = static_cast(obs_data_get_int(obj, "checkType")); _timeRestriction = static_cast