SceneSwitcher/lib/legacy/switch-media.cpp
2024-06-07 21:44:21 +02:00

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()),
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 (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