SceneSwitcher/lib/legacy/switch-media.cpp
WarmUpTill 7d0332dd0e Restructure library and plugins
The "core" macro conditions and actions have been extracted out to the
"base" plugin.

The library now mostly contains functionality which is required across
all plugins and (e.g. definitions for macro segments).

The goal is to reduce the complexity and cross-dependencies and group
the source files in a better way.

This should relsove the "library limit of 65535 objects exceeded" build
issue occuring in some Windows build environments.
2024-01-27 14:10:34 +01:00

568 lines
16 KiB
C++

#include "advanced-scene-switcher.hpp"
#include "switcher-data.hpp"
#include "utility.hpp"
namespace advss {
bool MediaSwitch::pause = false;
static QMetaObject::Connection addPulse;
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()),
ui->mediaAdd, &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 (verbose) {
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 = PulseWidget(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