#include "advanced-scene-switcher.hpp" #include "layout-helpers.hpp" #include "selection-helpers.hpp" #include "source-helpers.hpp" #include "switcher-data.hpp" #include "ui-helpers.hpp" #include "utility.hpp" namespace advss { bool MediaSwitch::pause = false; static QObject *addPulse = nullptr; constexpr auto media_played_to_end_idx = 8; constexpr auto media_any_idx = 9; void AdvSceneSwitcher::on_mediaAdd_clicked() { std::lock_guard lock(switcher->m); switcher->mediaSwitches.emplace_back(); listAddClicked(ui->mediaSwitches, new MediaSwitchWidget(this, &switcher->mediaSwitches.back()), &addPulse); ui->mediaHelp->setVisible(false); } void AdvSceneSwitcher::on_mediaRemove_clicked() { QListWidgetItem *item = ui->mediaSwitches->currentItem(); if (!item) { return; } { std::lock_guard lock(switcher->m); int idx = ui->mediaSwitches->currentRow(); auto &switches = switcher->mediaSwitches; switches.erase(switches.begin() + idx); } delete item; } void AdvSceneSwitcher::on_mediaUp_clicked() { int index = ui->mediaSwitches->currentRow(); if (!listMoveUp(ui->mediaSwitches)) { return; } MediaSwitchWidget *s1 = (MediaSwitchWidget *)ui->mediaSwitches->itemWidget( ui->mediaSwitches->item(index)); MediaSwitchWidget *s2 = (MediaSwitchWidget *)ui->mediaSwitches->itemWidget( ui->mediaSwitches->item(index - 1)); MediaSwitchWidget::swapSwitchData(s1, s2); std::lock_guard lock(switcher->m); std::swap(switcher->mediaSwitches[index], switcher->mediaSwitches[index - 1]); } void AdvSceneSwitcher::on_mediaDown_clicked() { int index = ui->mediaSwitches->currentRow(); if (!listMoveDown(ui->mediaSwitches)) { return; } MediaSwitchWidget *s1 = (MediaSwitchWidget *)ui->mediaSwitches->itemWidget( ui->mediaSwitches->item(index)); MediaSwitchWidget *s2 = (MediaSwitchWidget *)ui->mediaSwitches->itemWidget( ui->mediaSwitches->item(index + 1)); MediaSwitchWidget::swapSwitchData(s1, s2); std::lock_guard lock(switcher->m); std::swap(switcher->mediaSwitches[index], switcher->mediaSwitches[index + 1]); } bool SwitcherData::checkMediaSwitch(OBSWeakSource &scene, OBSWeakSource &transition) { if (MediaSwitch::pause) { return false; } bool match = false; for (MediaSwitch &mediaSwitch : mediaSwitches) { if (!mediaSwitch.initialized()) { continue; } obs_source_t *source = obs_weak_source_get_source(mediaSwitch.source); auto duration = obs_source_media_get_duration(source); auto time = obs_source_media_get_time(source); obs_media_state state = obs_source_media_get_state(source); obs_source_release(source); bool matchedStopped = mediaSwitch.state == OBS_MEDIA_STATE_STOPPED && mediaSwitch.stopped; // The signal for the state ended is intentionally not used here // so matchedEnded can be used to specify the end of a VLC playlist // by two consequtive matches of OBS_MEDIA_STATE_ENDED // // This was done to reduce the likelyhood of interpreting a single // OBS_MEDIA_STATE_ENDED caught by obs_source_media_get_state() // as the end of the playlist of the VLC source, while actually being // in the middle of switching to the next item of the playlist // // If there is a separate obs_media_sate in the future for the // "end of playlist reached" signal the line below can be used // and an additional check for this new singal can be introduced // // bool matchedEnded = mediaSwitch.state == OBS_MEDIA_STATE_ENDED && // mediaSwitch.ended; bool ended = false; if (state == OBS_MEDIA_STATE_ENDED) { ended = mediaSwitch.previousStateEnded; mediaSwitch.previousStateEnded = true; } else { mediaSwitch.previousStateEnded = false; } bool matchedEnded = ended && (mediaSwitch.state == OBS_MEDIA_STATE_ENDED); // match if playedToEnd was true in last interval // and playback is currently ended bool matchedPlayedToEnd = mediaSwitch.state == media_played_to_end_idx && mediaSwitch.playedToEnd && ended; // interval * 2 to make sure not to miss any state changes // which happened during check of the conditions mediaSwitch.playedToEnd = mediaSwitch.playedToEnd || (duration - time <= interval * 2); // reset if (ended) { mediaSwitch.playedToEnd = false; } // reset for next check mediaSwitch.stopped = false; mediaSwitch.ended = false; bool matchedState = ((state == mediaSwitch.state) || mediaSwitch.anyState) || matchedStopped || matchedEnded || matchedPlayedToEnd; bool matchedTimeNone = (mediaSwitch.restriction == TIME_RESTRICTION_NONE); bool matchedTimeLonger = (mediaSwitch.restriction == TIME_RESTRICTION_LONGER) && (time > mediaSwitch.time); bool matchedTimeShorter = (mediaSwitch.restriction == TIME_RESTRICTION_SHORTER) && (time < mediaSwitch.time); bool matchedTimeRemainLonger = (mediaSwitch.restriction == TIME_RESTRICTION_REMAINING_LONGER) && (duration > time && duration - time > mediaSwitch.time); bool matchedTimeRemainShorter = (mediaSwitch.restriction == TIME_RESTRICTION_REMAINING_SHORTER) && (duration > time && duration - time < mediaSwitch.time); bool matchedTime = matchedTimeNone || matchedTimeLonger || matchedTimeShorter || matchedTimeRemainLonger || matchedTimeRemainShorter; bool matched = matchedState && matchedTime; if (matched && !mediaSwitch.matched) { match = true; scene = mediaSwitch.getScene(); transition = mediaSwitch.transition; if (VerboseLoggingEnabled()) { mediaSwitch.logMatch(); } } mediaSwitch.matched = matched; if (match) { break; } } return match; } void SwitcherData::saveMediaSwitches(obs_data_t *obj) { obs_data_array_t *mediaArray = obs_data_array_create(); for (MediaSwitch &s : mediaSwitches) { obs_data_t *array_obj = obs_data_create(); s.save(array_obj); obs_data_array_push_back(mediaArray, array_obj); obs_data_release(array_obj); } obs_data_set_array(obj, "mediaSwitches", mediaArray); obs_data_array_release(mediaArray); } void SwitcherData::loadMediaSwitches(obs_data_t *obj) { obs_data_array_t *mediaArray = obs_data_get_array(obj, "mediaSwitches"); mediaSwitches.clear(); size_t count = obs_data_array_count(mediaArray); for (size_t i = 0; i < count; i++) { obs_data_t *array_obj = obs_data_array_item(mediaArray, i); mediaSwitches.emplace_back(); mediaSwitches.back().load(array_obj); obs_data_release(array_obj); } obs_data_array_release(mediaArray); } void AdvSceneSwitcher::SetupMediaTab() { for (auto &s : switcher->mediaSwitches) { QListWidgetItem *item; item = new QListWidgetItem(ui->mediaSwitches); ui->mediaSwitches->addItem(item); MediaSwitchWidget *sw = new MediaSwitchWidget(this, &s); item->setSizeHint(sw->minimumSizeHint()); ui->mediaSwitches->setItemWidget(item, sw); } if (switcher->mediaSwitches.size() == 0) { if (!switcher->disableHints) { addPulse = HighlightWidget(ui->mediaAdd, QColor(Qt::green)); } ui->mediaHelp->setVisible(true); } else { ui->mediaHelp->setVisible(false); } } bool MediaSwitch::initialized() { return SceneSwitcherEntry::initialized() && source; } bool MediaSwitch::valid() { return !initialized() || (SceneSwitcherEntry::valid() && WeakSourceValid(source)); } void MediaSwitch::save(obs_data_t *obj) { SceneSwitcherEntry::save(obj); obs_data_set_string(obj, "source", GetWeakSourceName(source).c_str()); obs_data_set_int(obj, "state", state); obs_data_set_int(obj, "restriction", restriction); obs_data_set_int(obj, "time", time); } void MediaSwitch::load(obs_data_t *obj) { SceneSwitcherEntry::load(obj); const char *sourceName = obs_data_get_string(obj, "source"); source = GetWeakSourceByName(sourceName); state = (obs_media_state)obs_data_get_int(obj, "state"); restriction = (time_restriction)obs_data_get_int(obj, "restriction"); time = obs_data_get_int(obj, "time"); anyState = state == media_any_idx; 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); } void MediaSwitch::clearSignalHandler() { obs_source_t *mediasource = obs_weak_source_get_source(source); signal_handler_t *sh = obs_source_get_signal_handler(mediasource); signal_handler_disconnect(sh, "media_stopped", MediaStopped, this); signal_handler_disconnect(sh, "media_ended", MediaEnded, this); obs_source_release(mediasource); } void MediaSwitch::resetSignalHandler() { obs_source_t *mediasource = obs_weak_source_get_source(source); signal_handler_t *sh = obs_source_get_signal_handler(mediasource); signal_handler_disconnect(sh, "media_stopped", MediaStopped, this); signal_handler_disconnect(sh, "media_ended", MediaEnded, this); signal_handler_connect(sh, "media_stopped", MediaStopped, this); signal_handler_connect(sh, "media_ended", MediaEnded, this); obs_source_release(mediasource); } void MediaSwitch::MediaStopped(void *data, calldata_t *) { MediaSwitch *media = static_cast(data); media->stopped = true; } void MediaSwitch::MediaEnded(void *data, calldata_t *) { MediaSwitch *media = static_cast(data); media->ended = true; } MediaSwitch::MediaSwitch(const MediaSwitch &other) : SceneSwitcherEntry(other.targetType, other.group, other.scene, other.transition, other.usePreviousScene), source(other.source), state(other.state), restriction(other.restriction), time(other.time) { anyState = state == media_any_idx; 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); } MediaSwitch::MediaSwitch(MediaSwitch &&other) noexcept : SceneSwitcherEntry(other.targetType, other.group, other.scene, other.transition, other.usePreviousScene), source(other.source), state(other.state), anyState(other.anyState), restriction(other.restriction), time(other.time) { } MediaSwitch::~MediaSwitch() { obs_source_t *mediasource = obs_weak_source_get_source(source); signal_handler_t *sh = obs_source_get_signal_handler(mediasource); signal_handler_disconnect(sh, "media_stopped", MediaStopped, this); signal_handler_disconnect(sh, "media_ended", MediaEnded, this); obs_source_release(mediasource); } MediaSwitch &MediaSwitch::operator=(const MediaSwitch &other) { MediaSwitch t(other); swap(*this, t); return *this = MediaSwitch(other); } MediaSwitch &MediaSwitch::operator=(MediaSwitch &&other) noexcept { if (this == &other) { return *this; } swap(*this, other); obs_source_t *mediasource = obs_weak_source_get_source(other.source); signal_handler_t *sh = obs_source_get_signal_handler(mediasource); signal_handler_disconnect(sh, "media_stopped", MediaStopped, &other); signal_handler_disconnect(sh, "media_ended", MediaEnded, &other); obs_source_release(mediasource); return *this; } void swap(MediaSwitch &first, MediaSwitch &second) { std::swap(first.targetType, second.targetType); std::swap(first.group, second.group); std::swap(first.scene, second.scene); std::swap(first.transition, second.transition); std::swap(first.usePreviousScene, second.usePreviousScene); std::swap(first.source, second.source); std::swap(first.state, second.state); std::swap(first.restriction, second.restriction); std::swap(first.time, second.time); std::swap(first.anyState, second.anyState); first.resetSignalHandler(); second.resetSignalHandler(); } static void populateMediaStates(QComboBox *list) { list->addItem(obs_module_text("AdvSceneSwitcher.mediaTab.states.none")); list->addItem( obs_module_text("AdvSceneSwitcher.mediaTab.states.playing")); list->addItem( obs_module_text("AdvSceneSwitcher.mediaTab.states.opening")); list->addItem( obs_module_text("AdvSceneSwitcher.mediaTab.states.buffering")); list->addItem( obs_module_text("AdvSceneSwitcher.mediaTab.states.paused")); list->addItem( obs_module_text("AdvSceneSwitcher.mediaTab.states.stopped")); list->addItem( obs_module_text("AdvSceneSwitcher.mediaTab.states.ended")); list->addItem( obs_module_text("AdvSceneSwitcher.mediaTab.states.error")); list->addItem(obs_module_text( "AdvSceneSwitcher.mediaTab.states.playedToEnd")); list->addItem(obs_module_text("AdvSceneSwitcher.mediaTab.states.any")); } static void populateTimeRestrictions(QComboBox *list) { list->addItem(obs_module_text( "AdvSceneSwitcher.mediaTab.timeRestriction.none")); list->addItem(obs_module_text( "AdvSceneSwitcher.mediaTab.timeRestriction.shorter")); list->addItem(obs_module_text( "AdvSceneSwitcher.mediaTab.timeRestriction.longer")); list->addItem(obs_module_text( "AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter")); list->addItem(obs_module_text( "AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger")); } MediaSwitchWidget::MediaSwitchWidget(QWidget *parent, MediaSwitch *s) : SwitchWidget(parent, s, true, true) { mediaSources = new QComboBox(); states = new QComboBox(); timeRestrictions = new QComboBox(); time = new QSpinBox(); time->setSuffix("ms"); time->setMaximum(99999999); time->setMinimum(0); QWidget::connect(mediaSources, SIGNAL(currentTextChanged(const QString &)), this, SLOT(SourceChanged(const QString &))); QWidget::connect(states, SIGNAL(currentIndexChanged(int)), this, SLOT(StateChanged(int))); QWidget::connect(timeRestrictions, SIGNAL(currentIndexChanged(int)), this, SLOT(TimeRestrictionChanged(int))); QWidget::connect(time, SIGNAL(valueChanged(int)), this, SLOT(TimeChanged(int))); PopulateMediaSelection(mediaSources); populateMediaStates(states); populateTimeRestrictions(timeRestrictions); if (s) { mediaSources->setCurrentText( GetWeakSourceName(s->source).c_str()); states->setCurrentIndex(s->state); timeRestrictions->setCurrentIndex(s->restriction); time->setValue(s->time); if (s->restriction == TIME_RESTRICTION_NONE) { time->setDisabled(true); } } QHBoxLayout *mainLayout = new QHBoxLayout; std::unordered_map widgetPlaceholders = { {"{{mediaSources}}", mediaSources}, {"{{states}}", states}, {"{{timeRestrictions}}", timeRestrictions}, {"{{time}}", time}, {"{{scenes}}", scenes}, {"{{transitions}}", transitions}}; PlaceWidgets(obs_module_text("AdvSceneSwitcher.mediaTab.entry"), mainLayout, widgetPlaceholders); setLayout(mainLayout); switchData = s; loading = false; } MediaSwitch *MediaSwitchWidget::getSwitchData() { return switchData; } void MediaSwitchWidget::setSwitchData(MediaSwitch *s) { switchData = s; } void MediaSwitchWidget::swapSwitchData(MediaSwitchWidget *s1, MediaSwitchWidget *s2) { SwitchWidget::swapSwitchData(s1, s2); MediaSwitch *t = s1->getSwitchData(); s1->setSwitchData(s2->getSwitchData()); s2->setSwitchData(t); } void MediaSwitchWidget::SourceChanged(const QString &text) { if (loading || !switchData) { return; } std::lock_guard lock(switcher->m); switchData->clearSignalHandler(); switchData->source = GetWeakSourceByQString(text); switchData->resetSignalHandler(); } void MediaSwitchWidget::StateChanged(int index) { if (loading || !switchData) { return; } std::lock_guard lock(switcher->m); switchData->state = (obs_media_state)index; switchData->anyState = switchData->state == media_any_idx; } void MediaSwitchWidget::TimeRestrictionChanged(int index) { if (loading || !switchData) { return; } if ((time_restriction)index == TIME_RESTRICTION_NONE) { time->setDisabled(true); } else { time->setDisabled(false); } std::lock_guard lock(switcher->m); switchData->restriction = (time_restriction)index; } void MediaSwitchWidget::TimeChanged(int time) { if (loading || !switchData) { return; } std::lock_guard lock(switcher->m); switchData->time = time; } } // namespace advss