diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index c7083bb1f..309fb8196 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -259,6 +259,7 @@ target_sources( window-basic-transform.cpp window-basic-transform.hpp window-basic-preview.hpp + window-basic-vcam.hpp window-basic-vcam-config.cpp window-basic-vcam-config.hpp window-dock.cpp diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index a46516737..e84759cab 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -181,7 +181,7 @@ static void OBSStopVirtualCam(void *data, calldata_t *params) Q_ARG(int, code)); obs_output_set_media(output->virtualCam, nullptr, nullptr); - OBSBasicVCamConfig::StopVideo(); + output->DestroyVirtualCamView(); } /* ------------------------------------------------------------------------ */ @@ -229,23 +229,30 @@ inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_) bool BasicOutputHandler::StartVirtualCam() { - if (main->vcamEnabled) { - video_t *video = OBSBasicVCamConfig::StartVideo(); - if (!video) + if (!main->vcamEnabled) + return false; + + if (!virtualCamView) + virtualCamView = obs_view_create(); + + UpdateVirtualCamOutputSource(); + + if (!virtualCamVideo) { + virtualCamVideo = obs_view_add(virtualCamView); + + if (!virtualCamVideo) return false; - - obs_output_set_media(virtualCam, video, obs_get_audio()); - if (!Active()) - SetupOutputs(); - - bool success = obs_output_start(virtualCam); - - if (!success) - OBSBasicVCamConfig::StopVideo(); - - return success; } - return false; + + obs_output_set_media(virtualCam, virtualCamVideo, obs_get_audio()); + if (!Active()) + SetupOutputs(); + + bool success = obs_output_start(virtualCam); + if (!success) + DestroyVirtualCamView(); + + return success; } void BasicOutputHandler::StopVirtualCam() @@ -263,6 +270,85 @@ bool BasicOutputHandler::VirtualCamActive() const return false; } +void BasicOutputHandler::UpdateVirtualCamOutputSource() +{ + if (!main->vcamEnabled || !virtualCamView) + return; + + OBSSourceAutoRelease source; + + switch (main->vcamConfig.type) { + case VCamOutputType::InternalOutput: + switch (main->vcamConfig.internal) { + case VCamInternalType::Default: + source = obs_get_output_source(0); + break; + case VCamInternalType::Preview: + OBSSource s = main->GetCurrentSceneSource(); + obs_source_get_ref(s); + source = s.Get(); + break; + } + break; + case VCamOutputType::SceneOutput: + source = obs_get_source_by_name(main->vcamConfig.scene.c_str()); + break; + case VCamOutputType::SourceOutput: + OBSSource s = + obs_get_source_by_name(main->vcamConfig.source.c_str()); + + if (!vCamSourceScene) + vCamSourceScene = + obs_scene_create_private("vcam_source"); + source = obs_source_get_ref( + obs_scene_get_source(vCamSourceScene)); + + if (vCamSourceSceneItem && + (obs_sceneitem_get_source(vCamSourceSceneItem) != s)) { + obs_sceneitem_remove(vCamSourceSceneItem); + vCamSourceSceneItem = nullptr; + } + + if (!vCamSourceSceneItem) { + vCamSourceSceneItem = obs_scene_add(vCamSourceScene, s); + obs_source_release(s); + + obs_sceneitem_set_bounds_type(vCamSourceSceneItem, + OBS_BOUNDS_SCALE_INNER); + obs_sceneitem_set_bounds_alignment(vCamSourceSceneItem, + OBS_ALIGN_CENTER); + + const struct vec2 size = { + (float)obs_source_get_width(source), + (float)obs_source_get_height(source), + }; + obs_sceneitem_set_bounds(vCamSourceSceneItem, &size); + } + break; + } + + OBSSourceAutoRelease current = obs_view_get_source(virtualCamView, 0); + if (source != current) + obs_view_set_source(virtualCamView, 0, source); +} + +void BasicOutputHandler::DestroyVirtualCamView() +{ + obs_view_remove(virtualCamView); + obs_view_set_source(virtualCamView, 0, nullptr); + virtualCamVideo = nullptr; + + obs_view_destroy(virtualCamView); + virtualCamView = nullptr; + + if (!vCamSourceScene) + return; + + obs_scene_release(vCamSourceScene); + vCamSourceScene = nullptr; + vCamSourceSceneItem = nullptr; +} + /* ------------------------------------------------------------------------ */ struct SimpleOutput : BasicOutputHandler { diff --git a/UI/window-basic-main-outputs.hpp b/UI/window-basic-main-outputs.hpp index 88e5d962b..fab2d3cfa 100644 --- a/UI/window-basic-main-outputs.hpp +++ b/UI/window-basic-main-outputs.hpp @@ -16,6 +16,11 @@ struct BasicOutputHandler { bool virtualCamActive = false; OBSBasic *main; + obs_view_t *virtualCamView = nullptr; + video_t *virtualCamVideo = nullptr; + obs_scene_t *vCamSourceScene = nullptr; + obs_sceneitem_t *vCamSourceSceneItem = nullptr; + std::string outputType; std::string lastError; @@ -57,6 +62,9 @@ struct BasicOutputHandler { virtual void Update() = 0; virtual void SetupOutputs() = 0; + virtual void UpdateVirtualCamOutputSource(); + virtual void DestroyVirtualCamView(); + inline bool Active() const { return streamingActive || recordingActive || delayActive || diff --git a/UI/window-basic-main-transitions.cpp b/UI/window-basic-main-transitions.cpp index 0d9b6d001..89091a61b 100644 --- a/UI/window-basic-main-transitions.cpp +++ b/UI/window-basic-main-transitions.cpp @@ -21,6 +21,7 @@ #include #include #include "window-basic-main.hpp" +#include "window-basic-main-outputs.hpp" #include "window-basic-vcam-config.hpp" #include "display-helpers.hpp" #include "window-namedialog.hpp" @@ -286,7 +287,8 @@ void OBSBasic::OverrideTransition(OBSSource transition) obs_transition_swap_end(transition, oldTransition); // Transition overrides don't raise an event so we need to call update directly - OBSBasicVCamConfig::UpdateOutputSource(); + if (vcamEnabled) + outputHandler->UpdateVirtualCamOutputSource(); } } @@ -428,6 +430,9 @@ void OBSBasic::SetTransition(OBSSource transition) ui->transitionRemove->setEnabled(configurable); ui->transitionProps->setEnabled(configurable); + if (vcamEnabled && vcamConfig.internal == VCamInternalType::Default) + outputHandler->UpdateVirtualCamOutputSource(); + if (api) api->on_event(OBS_FRONTEND_EVENT_TRANSITION_CHANGED); } @@ -695,6 +700,13 @@ void OBSBasic::SetCurrentScene(OBSSource scene, bool force) currentScene = itemScene.Get(); ui->scenes->setCurrentItem(item); ui->scenes->blockSignals(false); + + if (vcamEnabled && + vcamConfig.internal == + VCamInternalType::Preview) + outputHandler + ->UpdateVirtualCamOutputSource(); + if (api) api->on_event( OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 13db4a2b6..c0d770996 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -763,8 +763,27 @@ void OBSBasic::Save(const char *file) obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - if (vcamEnabled) - OBSBasicVCamConfig::SaveData(saveData, true); + if (vcamEnabled) { + OBSDataAutoRelease obj = obs_data_create(); + + obs_data_set_int(obj, "type", (int)vcamConfig.type); + switch (vcamConfig.type) { + case VCamOutputType::InternalOutput: + obs_data_set_int(obj, "internal", + (int)vcamConfig.internal); + break; + case VCamOutputType::SceneOutput: + obs_data_set_string(obj, "scene", + vcamConfig.scene.c_str()); + break; + case VCamOutputType::SourceOutput: + obs_data_set_string(obj, "source", + vcamConfig.source.c_str()); + break; + } + + obs_data_set_obj(saveData, "virtual-camera", obj); + } if (api) { OBSDataAutoRelease moduleObj = obs_data_create(); @@ -1178,8 +1197,16 @@ retryScene: ui->preview->SetFixedScaling(fixedScaling); emit ui->preview->DisplayResized(); - if (vcamEnabled) - OBSBasicVCamConfig::SaveData(data, false); + if (vcamEnabled) { + OBSDataAutoRelease obj = + obs_data_get_obj(data, "virtual-camera"); + + vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); + vcamConfig.internal = + (VCamInternalType)obs_data_get_int(obj, "internal"); + vcamConfig.scene = obs_data_get_string(obj, "scene"); + vcamConfig.source = obs_data_get_string(obj, "source"); + } /* ---------------------- */ @@ -1225,6 +1252,9 @@ retryScene: disableSaving--; + if (vcamEnabled && vcamConfig.internal == VCamInternalType::Preview) + outputHandler->UpdateVirtualCamOutputSource(); + if (api) { api->on_event(OBS_FRONTEND_EVENT_SCENE_CHANGED); api->on_event(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); @@ -1696,8 +1726,6 @@ void OBSBasic::ReplayBufferClicked() void OBSBasic::AddVCamButton() { - OBSBasicVCamConfig::Init(); - vcamButton = new ControlsSplitButton( QTStr("Basic.Main.StartVirtualCam"), "vcamButton", &OBSBasic::VCamButtonClicked); @@ -2752,8 +2780,6 @@ OBSBasic::~OBSBasic() delete cef; cef = nullptr; #endif - - OBSBasicVCamConfig::DestroyView(); } void OBSBasic::SaveProjectNow() @@ -5098,6 +5124,9 @@ void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, SetCurrentScene(source); + if (vcamEnabled && vcamConfig.internal == VCamInternalType::Preview) + outputHandler->UpdateVirtualCamOutputSource(); + if (api) api->on_event(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); @@ -7855,8 +7884,19 @@ void OBSBasic::VCamButtonClicked() void OBSBasic::VCamConfigButtonClicked() { - OBSBasicVCamConfig config(this); - config.exec(); + OBSBasicVCamConfig dialog(vcamConfig, this); + + connect(&dialog, &OBSBasicVCamConfig::Accepted, this, + &OBSBasic::UpdateVirtualCamConfig); + + dialog.exec(); +} + +void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) +{ + vcamConfig = config; + + outputHandler->UpdateVirtualCamOutputSource(); } void OBSBasic::on_settingsButton_clicked() diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index fa0857056..5b96acf62 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -28,6 +28,7 @@ #include #include "window-main.hpp" #include "window-basic-interaction.hpp" +#include "window-basic-vcam.hpp" #include "window-basic-properties.hpp" #include "window-basic-transform.hpp" #include "window-basic-adv-audio.hpp" @@ -51,6 +52,7 @@ class QMessageBox; class QListWidgetItem; class VolControl; class OBSBasicStats; +class OBSBasicVCamConfig; #include "ui_OBSBasic.h" #include "ui_ColorSelect.h" @@ -309,6 +311,7 @@ private: QPointer vcamButton; bool vcamEnabled = false; + VCamConfig vcamConfig; QScopedPointer trayIcon; QPointer sysTrayStream; @@ -819,6 +822,8 @@ private slots: void LockVolumeControl(bool lock); void ResetProxyStyleSliders(); + void UpdateVirtualCamConfig(const VCamConfig &config); + private: /* OBS Callbacks */ static void SceneReordered(void *data, calldata_t *params); diff --git a/UI/window-basic-vcam-config.cpp b/UI/window-basic-vcam-config.cpp index 86bbd4db5..cd3b2eca3 100644 --- a/UI/window-basic-vcam-config.cpp +++ b/UI/window-basic-vcam-config.cpp @@ -5,43 +5,21 @@ #include #include -enum class VCamOutputType { - Internal, - Scene, - Source, -}; - -enum class VCamInternalType { - Default, - Preview, -}; - -struct VCamConfig { - VCamOutputType type = VCamOutputType::Internal; - VCamInternalType internal = VCamInternalType::Default; - std::string scene; - std::string source; -}; - -static VCamConfig *vCamConfig = nullptr; - -OBSBasicVCamConfig::OBSBasicVCamConfig(QWidget *parent) - : QDialog(parent), ui(new Ui::OBSBasicVCamConfig) +OBSBasicVCamConfig::OBSBasicVCamConfig(const VCamConfig &_config, + QWidget *parent) + : config(_config), QDialog(parent), ui(new Ui::OBSBasicVCamConfig) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); ui->setupUi(this); - auto type = (int)vCamConfig->type; - ui->outputType->setCurrentIndex(type); - OutputTypeChanged(type); - connect(ui->outputType, - static_cast( - &QComboBox::currentIndexChanged), - this, &OBSBasicVCamConfig::OutputTypeChanged); + ui->outputType->setCurrentIndex(config.type); + OutputTypeChanged(config.type); + connect(ui->outputType, SIGNAL(currentIndexChanged(int)), this, + SLOT(OutputTypeChanged(int))); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, - &OBSBasicVCamConfig::Save); + &OBSBasicVCamConfig::UpdateConfig); } void OBSBasicVCamConfig::OutputTypeChanged(int type) @@ -50,25 +28,25 @@ void OBSBasicVCamConfig::OutputTypeChanged(int type) list->clear(); switch ((VCamOutputType)type) { - case VCamOutputType::Internal: + case VCamOutputType::InternalOutput: list->addItem(QTStr("Basic.VCam.InternalDefault")); list->addItem(QTStr("Basic.VCam.InternalPreview")); - list->setCurrentIndex((int)vCamConfig->internal); + list->setCurrentIndex(config.internal); break; - case VCamOutputType::Scene: { + case VCamOutputType::SceneOutput: { // Scenes in default order BPtr scenes = obs_frontend_get_scene_names(); for (char **temp = scenes; *temp; temp++) { list->addItem(*temp); - if (vCamConfig->scene.compare(*temp) == 0) + if (config.scene.compare(*temp) == 0) list->setCurrentIndex(list->count() - 1); } break; } - case VCamOutputType::Source: { + case VCamOutputType::SourceOutput: { // Sources in alphabetical order std::vector sources; auto AddSource = [&](obs_source_t *source) { @@ -97,7 +75,7 @@ void OBSBasicVCamConfig::OutputTypeChanged(int type) for (auto &&source : sources) { list->addItem(source.c_str()); - if (vCamConfig->source == source) + if (config.source == source) list->setCurrentIndex(list->count() - 1); } break; @@ -105,193 +83,27 @@ void OBSBasicVCamConfig::OutputTypeChanged(int type) } } -void OBSBasicVCamConfig::Save() +void OBSBasicVCamConfig::UpdateConfig() { - auto type = (VCamOutputType)ui->outputType->currentIndex(); - auto out = ui->outputSelection; + VCamOutputType type = (VCamOutputType)ui->outputType->currentIndex(); switch (type) { - case VCamOutputType::Internal: - vCamConfig->internal = (VCamInternalType)out->currentIndex(); + case VCamOutputType::InternalOutput: + config.internal = + (VCamInternalType)ui->outputSelection->currentIndex(); break; - case VCamOutputType::Scene: - vCamConfig->scene = out->currentText().toStdString(); + case VCamOutputType::SceneOutput: + config.scene = ui->outputSelection->currentText().toStdString(); break; - case VCamOutputType::Source: - vCamConfig->source = out->currentText().toStdString(); + case VCamOutputType::SourceOutput: + config.source = + ui->outputSelection->currentText().toStdString(); break; default: // unknown value, don't save type return; } - vCamConfig->type = type; + config.type = type; - // If already running just update the source - if (obs_frontend_virtualcam_active()) - UpdateOutputSource(); -} - -void OBSBasicVCamConfig::SaveData(obs_data_t *data, bool saving) -{ - if (saving) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type", (int)vCamConfig->type); - obs_data_set_int(obj, "internal", (int)vCamConfig->internal); - obs_data_set_string(obj, "scene", vCamConfig->scene.c_str()); - obs_data_set_string(obj, "source", vCamConfig->source.c_str()); - - obs_data_set_obj(data, "virtual-camera", obj); - } else { - OBSDataAutoRelease obj = - obs_data_get_obj(data, "virtual-camera"); - - vCamConfig->type = - (VCamOutputType)obs_data_get_int(obj, "type"); - vCamConfig->internal = - (VCamInternalType)obs_data_get_int(obj, "internal"); - vCamConfig->scene = obs_data_get_string(obj, "scene"); - vCamConfig->source = obs_data_get_string(obj, "source"); - } -} - -static void EventCallback(enum obs_frontend_event event, void *) -{ - if (vCamConfig->type != VCamOutputType::Internal) - return; - - // Update output source if the preview scene changes - // or if the default transition is changed - switch (event) { - case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED: - if (vCamConfig->internal != VCamInternalType::Preview) - return; - break; - case OBS_FRONTEND_EVENT_TRANSITION_CHANGED: - if (vCamConfig->internal != VCamInternalType::Default) - return; - break; - default: - return; - } - - OBSBasicVCamConfig::UpdateOutputSource(); -} - -static auto staticConfig = VCamConfig{}; - -void OBSBasicVCamConfig::Init() -{ - if (vCamConfig) - return; - - vCamConfig = &staticConfig; - - obs_frontend_add_event_callback(EventCallback, nullptr); -} - -static obs_view_t *view = nullptr; -static video_t *video = nullptr; - -static obs_scene_t *sourceScene = nullptr; -static obs_sceneitem_t *sourceSceneItem = nullptr; - -video_t *OBSBasicVCamConfig::StartVideo() -{ - if (!view) - view = obs_view_create(); - - UpdateOutputSource(); - - if (!video) - video = obs_view_add(view); - return video; -} - -void OBSBasicVCamConfig::StopVideo() -{ - obs_view_remove(view); - obs_view_set_source(view, 0, nullptr); - video = nullptr; - - if (sourceScene) { - obs_scene_release(sourceScene); - sourceScene = nullptr; - sourceSceneItem = nullptr; - } -} - -void OBSBasicVCamConfig::DestroyView() -{ - StopVideo(); - obs_view_destroy(view); - view = nullptr; -} - -void OBSBasicVCamConfig::UpdateOutputSource() -{ - if (!view) - return; - - obs_source_t *source = nullptr; - - switch ((VCamOutputType)vCamConfig->type) { - case VCamOutputType::Internal: - switch (vCamConfig->internal) { - case VCamInternalType::Default: - source = obs_get_output_source(0); - break; - case VCamInternalType::Preview: - OBSSource s = OBSBasic::Get()->GetCurrentSceneSource(); - obs_source_get_ref(s); - source = s; - break; - } - break; - - case VCamOutputType::Scene: - source = obs_get_source_by_name(vCamConfig->scene.c_str()); - break; - - case VCamOutputType::Source: - auto rawSource = - obs_get_source_by_name(vCamConfig->source.c_str()); - if (!rawSource) - break; - - // Use a scene transform to fit the source size to the canvas - if (!sourceScene) - sourceScene = obs_scene_create_private(nullptr); - source = obs_source_get_ref(obs_scene_get_source(sourceScene)); - - if (sourceSceneItem) { - if (obs_sceneitem_get_source(sourceSceneItem) != - rawSource) { - obs_sceneitem_remove(sourceSceneItem); - sourceSceneItem = nullptr; - } - } - if (!sourceSceneItem) { - sourceSceneItem = obs_scene_add(sourceScene, rawSource); - obs_source_release(rawSource); - - obs_sceneitem_set_bounds_type(sourceSceneItem, - OBS_BOUNDS_SCALE_INNER); - obs_sceneitem_set_bounds_alignment(sourceSceneItem, - OBS_ALIGN_CENTER); - - const struct vec2 size = { - (float)obs_source_get_width(source), - (float)obs_source_get_height(source), - }; - obs_sceneitem_set_bounds(sourceSceneItem, &size); - } - break; - } - - auto current = obs_view_get_source(view, 0); - if (source != current) - obs_view_set_source(view, 0, source); - obs_source_release(source); - obs_source_release(current); + emit Accepted(config); } diff --git a/UI/window-basic-vcam-config.hpp b/UI/window-basic-vcam-config.hpp index 03cbdb0c9..2c48b45be 100644 --- a/UI/window-basic-vcam-config.hpp +++ b/UI/window-basic-vcam-config.hpp @@ -4,27 +4,28 @@ #include #include +#include "window-basic-vcam.hpp" + #include "ui_OBSBasicVCamConfig.h" +struct VCamConfig; + class OBSBasicVCamConfig : public QDialog { Q_OBJECT + VCamConfig config; + public: - static void Init(); - - static video_t *StartVideo(); - static void StopVideo(); - static void DestroyView(); - - static void UpdateOutputSource(); - static void SaveData(obs_data_t *data, bool saving); - - explicit OBSBasicVCamConfig(QWidget *parent = 0); + explicit OBSBasicVCamConfig(const VCamConfig &config, + QWidget *parent = 0); private slots: void OutputTypeChanged(int type); - void Save(); + void UpdateConfig(); private: std::unique_ptr ui; + +signals: + void Accepted(const VCamConfig &config); }; diff --git a/UI/window-basic-vcam.hpp b/UI/window-basic-vcam.hpp new file mode 100644 index 000000000..0b538006d --- /dev/null +++ b/UI/window-basic-vcam.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +enum VCamOutputType { + InternalOutput, + SceneOutput, + SourceOutput, +}; + +enum VCamInternalType { + Default, + Preview, +}; + +struct VCamConfig { + VCamOutputType type = VCamOutputType::InternalOutput; + VCamInternalType internal = VCamInternalType::Default; + std::string scene; + std::string source; +};