mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-22 09:54:54 -05:00
573 lines
16 KiB
C++
573 lines
16 KiB
C++
#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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<MediaSwitch *>(data);
|
|
media->stopped = true;
|
|
}
|
|
|
|
void MediaSwitch::MediaEnded(void *data, calldata_t *)
|
|
{
|
|
MediaSwitch *media = static_cast<MediaSwitch *>(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<std::string, QWidget *> 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<std::mutex> lock(switcher->m);
|
|
switchData->clearSignalHandler();
|
|
switchData->source = GetWeakSourceByQString(text);
|
|
switchData->resetSignalHandler();
|
|
}
|
|
|
|
void MediaSwitchWidget::StateChanged(int index)
|
|
{
|
|
if (loading || !switchData) {
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> 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<std::mutex> lock(switcher->m);
|
|
switchData->restriction = (time_restriction)index;
|
|
}
|
|
|
|
void MediaSwitchWidget::TimeChanged(int time)
|
|
{
|
|
if (loading || !switchData) {
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(switcher->m);
|
|
switchData->time = time;
|
|
}
|
|
|
|
} // namespace advss
|