rtmp-services: Add Twitch ingest update API
Updates ingests via Twitch's ingest API. The servers are ordered by closest to farthest from the user via the API. It's probably still good to keep the services.json Twitch ingests updated in case there are issues with the Twitch ingest API, but otherwise the ingests will update from Twitch itself every time the program is opened automatically.master
parent
3786a203c7
commit
435e251809
|
@ -3,11 +3,13 @@ project(rtmp-services)
|
|||
include_directories(${OBS_JANSSON_INCLUDE_DIRS})
|
||||
|
||||
set(rtmp-services_SOURCES
|
||||
twitch.c
|
||||
rtmp-common.c
|
||||
rtmp-custom.c
|
||||
rtmp-services-main.c)
|
||||
|
||||
set(rtmp-services_HEADERS
|
||||
twitch.h
|
||||
rtmp-format-ver.h)
|
||||
|
||||
set(RTMP_SERVICES_URL
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <jansson.h>
|
||||
|
||||
#include "rtmp-format-ver.h"
|
||||
#include "twitch.h"
|
||||
|
||||
struct rtmp_common {
|
||||
char *service;
|
||||
|
@ -242,6 +243,32 @@ static void properties_data_destroy(void *data)
|
|||
json_decref(root);
|
||||
}
|
||||
|
||||
static bool fill_twitch_servers_locked(obs_property_t *servers_prop)
|
||||
{
|
||||
size_t count = twitch_ingest_count();
|
||||
|
||||
if (count <= 1)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
struct twitch_ingest ing = twitch_ingest(i);
|
||||
obs_property_list_add_string(servers_prop, ing.name, ing.url);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool fill_twitch_servers(obs_property_t *servers_prop)
|
||||
{
|
||||
bool success;
|
||||
|
||||
twitch_ingests_lock();
|
||||
success = fill_twitch_servers_locked(servers_prop);
|
||||
twitch_ingests_unlock();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static void fill_servers(obs_property_t *servers_prop, json_t *service,
|
||||
const char *name)
|
||||
{
|
||||
|
@ -263,6 +290,10 @@ static void fill_servers(obs_property_t *servers_prop, json_t *service,
|
|||
obs_property_list_add_string(servers_prop,
|
||||
obs_module_text("Server.Auto"), "auto");
|
||||
}
|
||||
if (name && strcmp(name, "Twitch") == 0) {
|
||||
if (fill_twitch_servers(servers_prop))
|
||||
return;
|
||||
}
|
||||
|
||||
json_array_foreach (servers, index, server) {
|
||||
const char *server_name = get_string_val(server, "name");
|
||||
|
|
|
@ -40,6 +40,9 @@ static bool confirm_service_file(void *param, struct file_download_data *file)
|
|||
return true;
|
||||
}
|
||||
|
||||
extern void load_twitch_data(const char *module_str);
|
||||
extern void unload_twitch_data(void);
|
||||
|
||||
bool obs_module_load(void)
|
||||
{
|
||||
char *local_dir = obs_module_file("");
|
||||
|
@ -60,6 +63,8 @@ bool obs_module_load(void)
|
|||
confirm_service_file, NULL);
|
||||
}
|
||||
|
||||
load_twitch_data(module_name.array);
|
||||
|
||||
bfree(local_dir);
|
||||
bfree(cache_dir);
|
||||
dstr_free(&module_name);
|
||||
|
@ -72,4 +77,5 @@ bool obs_module_load(void)
|
|||
void obs_module_unload(void)
|
||||
{
|
||||
update_info_destroy(update_info);
|
||||
unload_twitch_data();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
#include <file-updater/file-updater.h>
|
||||
#include <util/threading.h>
|
||||
#include <util/platform.h>
|
||||
#include <obs-module.h>
|
||||
#include <util/dstr.h>
|
||||
#include <jansson.h>
|
||||
|
||||
#include "twitch.h"
|
||||
|
||||
static update_info_t *twitch_update_info = NULL;
|
||||
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
struct ingest {
|
||||
char *name;
|
||||
char *url;
|
||||
};
|
||||
|
||||
static DARRAY(struct ingest) cur_ingests;
|
||||
|
||||
static void free_ingests(void)
|
||||
{
|
||||
for (size_t i = 0; i < cur_ingests.num; i++) {
|
||||
struct ingest *ingest = cur_ingests.array + i;
|
||||
bfree(ingest->name);
|
||||
bfree(ingest->url);
|
||||
}
|
||||
|
||||
da_free(cur_ingests);
|
||||
}
|
||||
|
||||
static inline void init_defaults(void)
|
||||
{
|
||||
struct ingest ingest = {0};
|
||||
ingest.name = bstrdup("Default");
|
||||
ingest.url = bstrdup("rtmp://live.twitch.tv/app");
|
||||
da_push_back(cur_ingests, &ingest);
|
||||
}
|
||||
|
||||
static void load_ingests(const char *json, bool write_file)
|
||||
{
|
||||
json_t *root;
|
||||
json_t *ingests;
|
||||
bool success = false;
|
||||
char *cache_old;
|
||||
char *cache_new;
|
||||
size_t count;
|
||||
|
||||
root = json_loads(json, 0, NULL);
|
||||
if (!root)
|
||||
goto finish;
|
||||
|
||||
ingests = json_object_get(root, "ingests");
|
||||
if (!ingests)
|
||||
goto finish;
|
||||
|
||||
count = json_array_size(ingests);
|
||||
if (count <= 1 && cur_ingests.num)
|
||||
goto finish;
|
||||
|
||||
free_ingests();
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
json_t *item = json_array_get(ingests, i);
|
||||
json_t *item_name = json_object_get(item, "name");
|
||||
json_t *item_url = json_object_get(item, "url_template");
|
||||
struct ingest ingest = {0};
|
||||
struct dstr url = {0};
|
||||
|
||||
if (!item_name || !item_url)
|
||||
continue;
|
||||
|
||||
const char *url_str = json_string_value(item_url);
|
||||
const char *name_str = json_string_value(item_name);
|
||||
|
||||
/* At the moment they currently mis-spell "deprecated",
|
||||
* but that may change in the future, so blacklist both */
|
||||
if (strstr(name_str, "deprecated") != NULL ||
|
||||
strstr(name_str, "depracated") != NULL)
|
||||
continue;
|
||||
|
||||
dstr_copy(&url, url_str);
|
||||
dstr_replace(&url, "/{stream_key}", "");
|
||||
|
||||
ingest.name = bstrdup(name_str);
|
||||
ingest.url = url.array;
|
||||
|
||||
da_push_back(cur_ingests, &ingest);
|
||||
}
|
||||
|
||||
if (!write_file || !cur_ingests.num)
|
||||
goto finish;
|
||||
|
||||
cache_old = obs_module_config_path("twitch_ingests.json");
|
||||
cache_new = obs_module_config_path("twitch_ingests.new.json");
|
||||
|
||||
os_quick_write_utf8_file(cache_new, json, strlen(json), false);
|
||||
os_safe_replace(cache_old, cache_new, NULL);
|
||||
|
||||
bfree(cache_old);
|
||||
bfree(cache_new);
|
||||
|
||||
finish:
|
||||
if (!cur_ingests.num)
|
||||
init_defaults();
|
||||
if (root)
|
||||
json_decref(root);
|
||||
}
|
||||
|
||||
static bool twitch_ingest_update(void *param, struct file_download_data *data)
|
||||
{
|
||||
pthread_mutex_lock(&mutex);
|
||||
load_ingests(data->buffer.array, true);
|
||||
pthread_mutex_unlock(&mutex);
|
||||
|
||||
UNUSED_PARAMETER(param);
|
||||
return true;
|
||||
}
|
||||
|
||||
void twitch_ingests_lock(void)
|
||||
{
|
||||
pthread_mutex_lock(&mutex);
|
||||
}
|
||||
|
||||
void twitch_ingests_unlock(void)
|
||||
{
|
||||
pthread_mutex_unlock(&mutex);
|
||||
}
|
||||
|
||||
size_t twitch_ingest_count(void)
|
||||
{
|
||||
return cur_ingests.num;
|
||||
}
|
||||
|
||||
struct twitch_ingest twitch_ingest(size_t idx)
|
||||
{
|
||||
struct twitch_ingest ingest;
|
||||
|
||||
if (cur_ingests.num <= idx) {
|
||||
ingest.name = NULL;
|
||||
ingest.url = NULL;
|
||||
} else {
|
||||
ingest = *(struct twitch_ingest*)(cur_ingests.array + idx);
|
||||
}
|
||||
|
||||
return ingest;
|
||||
}
|
||||
|
||||
void load_twitch_data(const char *module_str)
|
||||
{
|
||||
char *twitch_cache = obs_module_config_path("twitch_ingests.json");
|
||||
|
||||
if (os_file_exists(twitch_cache)) {
|
||||
char *data = os_quick_read_utf8_file(twitch_cache);
|
||||
|
||||
pthread_mutex_lock(&mutex);
|
||||
load_ingests(data, false);
|
||||
pthread_mutex_unlock(&mutex);
|
||||
|
||||
bfree(data);
|
||||
} else {
|
||||
init_defaults();
|
||||
}
|
||||
|
||||
twitch_update_info = update_info_create_single(
|
||||
"[twitch ingest update] ",
|
||||
module_str,
|
||||
"https://ingest.twitch.tv/api/v2/ingests",
|
||||
twitch_ingest_update, NULL);
|
||||
|
||||
bfree(twitch_cache);
|
||||
}
|
||||
|
||||
void unload_twitch_data(void)
|
||||
{
|
||||
update_info_destroy(twitch_update_info);
|
||||
free_ingests();
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
struct twitch_ingest {
|
||||
const char *name;
|
||||
const char *url;
|
||||
};
|
||||
|
||||
extern void twitch_ingests_lock(void);
|
||||
extern void twitch_ingests_unlock(void);
|
||||
extern size_t twitch_ingest_count(void);
|
||||
extern struct twitch_ingest twitch_ingest(size_t idx);
|
Loading…
Reference in New Issue