#include "irl-source.h" #include "ingest-thread.h" #include "event-handler.h" #include "remote-settings.h" #include "obfuscation.h" #include "translations.h" #ifdef _WIN32 #include #include #endif struct irl_source_data *g_irl_sources[MAX_IRL_SOURCES] = {0}; int g_irl_source_count = 0; /* ---- helpers ---- */ static inline void safe_bfree(char **ptr) { bfree(*ptr); *ptr = NULL; } static void load_settings(struct irl_source_data *data, obs_data_t *settings) { pthread_mutex_lock(&data->mutex); data->protocol = (int)obs_data_get_int(settings, "protocol"); data->port = (int)obs_data_get_int(settings, "port"); safe_bfree(&data->stream_key); data->stream_key = bstrdup(obs_data_get_string(settings, "stream_key")); safe_bfree(&data->srt_passphrase); data->srt_passphrase = bstrdup(obs_data_get_string(settings, "srt_passphrase")); safe_bfree(&data->srt_streamid); data->srt_streamid = bstrdup(obs_data_get_string(settings, "srt_streamid")); data->srt_latency_ms = (int)obs_data_get_int(settings, "srt_latency"); data->disconnect_timeout_sec = (int)obs_data_get_int(settings, "disconnect_timeout"); safe_bfree(&data->disconnect_scene_name); data->disconnect_scene_name = bstrdup(obs_data_get_string(settings, "disconnect_scene")); safe_bfree(&data->reconnect_scene_name); data->reconnect_scene_name = bstrdup(obs_data_get_string(settings, "reconnect_scene")); safe_bfree(&data->overlay_source_name); data->overlay_source_name = bstrdup(obs_data_get_string(settings, "overlay_source")); data->disconnect_recording_action = (int)obs_data_get_int(settings, "recording_action"); data->low_quality_enabled = obs_data_get_bool(settings, "low_quality_enabled"); data->low_quality_bitrate_kbps = (int)obs_data_get_int(settings, "low_quality_bitrate"); data->low_quality_timeout_sec = (int)obs_data_get_int(settings, "low_quality_timeout"); safe_bfree(&data->low_quality_scene_name); data->low_quality_scene_name = bstrdup(obs_data_get_string(settings, "low_quality_scene")); safe_bfree(&data->low_quality_overlay_name); data->low_quality_overlay_name = bstrdup(obs_data_get_string(settings, "low_quality_overlay")); data->srtla_enabled = obs_data_get_bool(settings, "srtla_enabled"); data->srtla_port = (int)obs_data_get_int(settings, "srtla_port"); safe_bfree(&data->duckdns_domain); data->duckdns_domain = bstrdup(obs_data_get_string(settings, "duckdns_domain")); safe_bfree(&data->duckdns_token); data->duckdns_token = bstrdup(obs_data_get_string(settings, "duckdns_token")); safe_bfree(&data->webhook_url); data->webhook_url = bstrdup(obs_data_get_string(settings, "webhook_url")); safe_bfree(&data->custom_command); data->custom_command = bstrdup(obs_data_get_string(settings, "custom_command")); pthread_mutex_unlock(&data->mutex); } /* ---- source callbacks ---- */ static const char *irl_source_get_name(void *unused) { UNUSED_PARAMETER(unused); return tr_source_name(); } static void *irl_source_create(obs_data_t *settings, obs_source_t *source) { struct irl_source_data *data = bzalloc(sizeof(*data)); data->source = source; data->video_stream_idx = -1; data->audio_stream_idx = -1; data->active = true; data->show_watermark = true; pthread_mutex_init(&data->mutex, NULL); load_settings(data, settings); ingest_thread_start(data); if (g_irl_source_count < MAX_IRL_SOURCES) g_irl_sources[g_irl_source_count++] = data; remote_settings_start(data); return data; } static void irl_source_destroy(void *vdata) { struct irl_source_data *data = vdata; remote_settings_stop(data); for (int i = 0; i < g_irl_source_count; i++) { if (g_irl_sources[i] == data) { g_irl_sources[i] = g_irl_sources[--g_irl_source_count]; g_irl_sources[g_irl_source_count] = NULL; break; } } data->active = false; ingest_thread_stop(data); obs_source_output_video(data->source, NULL); safe_bfree(&data->stream_key); safe_bfree(&data->srt_passphrase); safe_bfree(&data->srt_streamid); safe_bfree(&data->disconnect_scene_name); safe_bfree(&data->reconnect_scene_name); safe_bfree(&data->overlay_source_name); safe_bfree(&data->low_quality_scene_name); safe_bfree(&data->low_quality_overlay_name); safe_bfree(&data->duckdns_domain); safe_bfree(&data->duckdns_token); safe_bfree(&data->webhook_url); safe_bfree(&data->custom_command); pthread_mutex_destroy(&data->mutex); bfree(data); } static void irl_source_update(void *vdata, obs_data_t *settings) { struct irl_source_data *data = vdata; UNUSED_PARAMETER(settings); UNUSED_PARAMETER(data); } static void irl_source_get_defaults(obs_data_t *settings) { obs_data_set_default_string(settings, "api_token", ""); obs_data_set_default_int(settings, "protocol", PROTOCOL_RTMP); obs_data_set_default_int(settings, "port", 1935); obs_data_set_default_string(settings, "stream_key", "stream"); obs_data_set_default_string(settings, "srt_streamid", "stream"); obs_data_set_default_int(settings, "srt_latency", 200); obs_data_set_default_int(settings, "disconnect_timeout", 5); obs_data_set_default_int(settings, "recording_action", RECORDING_ACTION_NONE); obs_data_set_default_bool(settings, "low_quality_enabled", false); obs_data_set_default_int(settings, "low_quality_bitrate", 500); obs_data_set_default_int(settings, "low_quality_timeout", 3); obs_data_set_default_bool(settings, "srtla_enabled", false); obs_data_set_default_int(settings, "srtla_port", 5000); } static bool login_button_clicked(obs_properties_t *props, obs_property_t *prop, void *data) { UNUSED_PARAMETER(props); UNUSED_PARAMETER(prop); UNUSED_PARAMETER(data); char url[256]; snprintf(url, sizeof(url), "%s%s%s", obf_https_prefix(), obf_stools_host(), obf_dash_tools_path()); #ifdef _WIN32 ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL); #elif __APPLE__ char cmd[512]; snprintf(cmd, sizeof(cmd), "open \"%s\"", url); system(cmd); #else char cmd[512]; snprintf(cmd, sizeof(cmd), "xdg-open \"%s\"", url); system(cmd); #endif return false; } static obs_properties_t *irl_source_get_properties(void *vdata) { UNUSED_PARAMETER(vdata); obs_properties_t *props = obs_properties_create(); obs_properties_add_text(props, "api_token", tr_api_token(), OBS_TEXT_PASSWORD); obs_properties_add_button(props, "login_button", tr_login_button(), login_button_clicked); obs_properties_add_text(props, "api_info", tr_api_info(), OBS_TEXT_INFO); return props; } static void irl_source_video_tick(void *vdata, float seconds) { UNUSED_PARAMETER(seconds); struct irl_source_data *data = vdata; event_handler_tick(data); } /* ---- source info ---- */ struct obs_source_info irl_source_info = { .id = SOURCE_ID, .type = OBS_SOURCE_TYPE_INPUT, .output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE, .get_name = irl_source_get_name, .create = irl_source_create, .destroy = irl_source_destroy, .update = irl_source_update, .get_defaults = irl_source_get_defaults, .get_properties = irl_source_get_properties, .video_tick = irl_source_video_tick, .icon_type = OBS_ICON_TYPE_CAMERA, };