Skip to content

Commit

Permalink
Add URL source thread and callbacks (#90)
Browse files Browse the repository at this point in the history
  • Loading branch information
royshil committed Apr 4, 2024
1 parent bc84b80 commit 71cc597
Show file tree
Hide file tree
Showing 8 changed files with 420 additions and 355 deletions.
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ target_sources(
src/request-data.cpp
src/ui/text-render-helper.cpp
src/url-source-info.c
src/obs-source-util.cpp)
src/obs-source-util.cpp
src/url-source-callbacks.cpp
src/url-source-thread.cpp)
add_subdirectory(src/parsers)

set_target_properties_plugin(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${_name})
6 changes: 6 additions & 0 deletions src/obs-source-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,10 @@ std::vector<uint8_t> get_rgba_from_source_render(obs_source_t *source, source_re
std::string convert_rgba_buffer_to_png_base64(const std::vector<uint8_t> &rgba, uint32_t width,
uint32_t height);

inline bool is_valid_output_source_name(const char *output_source_name)
{
return output_source_name != nullptr && strcmp(output_source_name, "none") != 0 &&
strcmp(output_source_name, "(null)") != 0 && strcmp(output_source_name, "") != 0;
}

#endif // OBS_SOURCE_UTIL_H
162 changes: 162 additions & 0 deletions src/url-source-callbacks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@

#include "url-source-callbacks.h"
#include "url-source-data.h"
#include "obs-source-util.h"
#include "plugin-support.h"

#include <obs-module.h>

#include <mutex>

void acquire_weak_output_source_ref(struct url_source_data *usd)
{
if (!is_valid_output_source_name(usd->output_source_name)) {
obs_log(LOG_ERROR, "output_source_name is invalid");
// text source is not selected
return;
}

if (!usd->output_source_mutex) {
obs_log(LOG_ERROR, "output_source_mutex is null");
return;
}

std::lock_guard<std::mutex> lock(*usd->output_source_mutex);

// acquire a weak ref to the new text source
obs_source_t *source = obs_get_source_by_name(usd->output_source_name);
if (source) {
usd->output_source = obs_source_get_weak_source(source);
obs_source_release(source);
if (!usd->output_source) {
obs_log(LOG_ERROR, "failed to get weak source for text source %s",
usd->output_source_name);
}
} else {
obs_log(LOG_ERROR, "text source '%s' not found", usd->output_source_name);
}
}

void setTextCallback(const std::string &str, struct url_source_data *usd)
{
if (!usd->output_source_mutex) {
obs_log(LOG_ERROR, "output_source_mutex is null");
return;
}

if (!usd->output_source) {
// attempt to acquire a weak ref to the text source if it's yet available
acquire_weak_output_source_ref(usd);
}

std::lock_guard<std::mutex> lock(*usd->output_source_mutex);

obs_weak_source_t *text_source = usd->output_source;
if (!text_source) {
obs_log(LOG_ERROR, "text_source is null");
return;
}
auto target = obs_weak_source_get_source(text_source);
if (!target) {
obs_log(LOG_ERROR, "text_source target is null");
return;
}
auto target_settings = obs_source_get_settings(target);
if (strcmp(obs_source_get_id(target), "ffmpeg_source") == 0) {
// if the target source is a media source - set the input field to the text and disable the local file
obs_data_set_bool(target_settings, "is_local_file", false);
obs_data_set_bool(target_settings, "clear_on_media_end", true);
obs_data_set_string(target_settings, "local_file", "");
obs_data_set_string(target_settings, "input", str.c_str());
obs_data_set_bool(target_settings, "looping", false);
} else {
// if the target source is a text source - set the text field
obs_data_set_string(target_settings, "text", str.c_str());
}
obs_source_update(target, target_settings);
if (usd->unhide_output_source) {
// unhide the output source
obs_source_set_enabled(target, true);
obs_enum_scenes(
[](void *target_ptr, obs_source_t *scene_source) -> bool {
obs_scene_t *scene = obs_scene_from_source(scene_source);
if (scene == nullptr) {
return true;
}
obs_sceneitem_t *scene_item = obs_scene_sceneitem_from_source(
scene, (obs_source_t *)target_ptr);
if (scene_item == nullptr) {
return true;
}
obs_sceneitem_set_visible(scene_item, true);
return false; // stop enumerating
},
target);
}
obs_data_release(target_settings);
obs_source_release(target);
};

void setAudioCallback(const std::string &str, struct url_source_data *usd)
{
if (!usd->output_source_mutex) {
obs_log(LOG_ERROR, "output_source_mutex is null");
return;
}

if (!usd->output_source) {
// attempt to acquire a weak ref to the output source if it's yet available
acquire_weak_output_source_ref(usd);
}

std::lock_guard<std::mutex> lock(*usd->output_source_mutex);

obs_weak_source_t *media_source = usd->output_source;
if (!media_source) {
obs_log(LOG_ERROR, "output_source is null");
return;
}
auto target = obs_weak_source_get_source(media_source);
if (!target) {
obs_log(LOG_ERROR, "output_source target is null");
return;
}
// assert the source is a media source
if (strcmp(obs_source_get_id(target), "ffmpeg_source") != 0) {
obs_source_release(target);
obs_log(LOG_ERROR, "output_source is not a media source");
return;
}
obs_data_t *media_settings = obs_source_get_settings(target);
obs_data_set_bool(media_settings, "is_local_file", true);
obs_data_set_bool(media_settings, "clear_on_media_end", true);
obs_data_set_string(media_settings, "local_file", str.c_str());
obs_data_set_bool(media_settings, "looping", false);
obs_source_update(target, media_settings);
obs_data_release(media_settings);

obs_source_media_restart(target);
obs_source_release(target);
};

std::string renderOutputTemplate(inja::Environment &env, const std::string &input,
const std::vector<std::string> &outputs,
const nlohmann::json &body)
try {
// Use Inja to render the template
nlohmann::json data;
if (outputs.size() > 1) {
for (size_t i = 0; i < outputs.size(); i++) {
data["output" + std::to_string(i)] = outputs[i];
}
// in "output" add an array of all the outputs
data["output"] = outputs;
} else {
data["output"] = outputs[0];
}
data["body"] = body;
return env.render(input, data);
} catch (std::exception &e) {
obs_log(LOG_ERROR, "Failed to parse template: %s", e.what());
return "";
}
17 changes: 17 additions & 0 deletions src/url-source-callbacks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef URL_SOURCE_CALLBACKS_H
#define URL_SOURCE_CALLBACKS_H

#include <obs-module.h>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include <inja/inja.hpp>

std::string renderOutputTemplate(inja::Environment &env, const std::string &input,
const std::vector<std::string> &outputs,
const nlohmann::json &body);

void setAudioCallback(const std::string &str, struct url_source_data *usd);
void setTextCallback(const std::string &str, struct url_source_data *usd);

#endif
38 changes: 38 additions & 0 deletions src/url-source-data.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#ifndef URL_SOURCE_DATA_H
#define URL_SOURCE_DATA_H

#include "request-data.h"

#include <obs-module.h>
#include <string>
#include <mutex>
#include <thread>
#include <condition_variable>

struct url_source_data {
obs_source_t *source;
struct url_source_request_data request_data;
struct request_data_handler_response response;
uint32_t update_timer_ms = 1000;
bool run_while_not_visible = false;
std::string css_props;
std::string output_text_template;
bool output_is_image_url = false;
struct obs_source_frame frame;
bool send_to_stream = false;
uint32_t render_width = 640;
bool unhide_output_source = false;

// Text source to output the text to
obs_weak_source_t *output_source = nullptr;
char *output_source_name = nullptr;
std::mutex *output_source_mutex = nullptr;

// Use std for thread and mutex
std::mutex *curl_mutex = nullptr;
std::thread curl_thread;
std::condition_variable *curl_thread_cv = nullptr;
bool curl_thread_run = false;
};

#endif
Loading

0 comments on commit 71cc597

Please sign in to comment.