twitch.c 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. #include <file-updater/file-updater.h>
  2. #include <util/threading.h>
  3. #include <util/platform.h>
  4. #include <obs-module.h>
  5. #include <util/dstr.h>
  6. #include <jansson.h>
  7. #include "twitch.h"
  8. static update_info_t *twitch_update_info = NULL;
  9. static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  10. static bool ingests_refreshed = false;
  11. static bool ingests_refreshing = false;
  12. static bool ingests_loaded = false;
  13. struct ingest {
  14. char *name;
  15. char *url;
  16. };
  17. static DARRAY(struct ingest) cur_ingests;
  18. static void free_ingests(void)
  19. {
  20. for (size_t i = 0; i < cur_ingests.num; i++) {
  21. struct ingest *ingest = cur_ingests.array + i;
  22. bfree(ingest->name);
  23. bfree(ingest->url);
  24. }
  25. da_free(cur_ingests);
  26. }
  27. static bool load_ingests(const char *json, bool write_file)
  28. {
  29. json_t *root;
  30. json_t *ingests;
  31. bool success = false;
  32. char *cache_old;
  33. char *cache_new;
  34. size_t count;
  35. root = json_loads(json, 0, NULL);
  36. if (!root)
  37. goto finish;
  38. ingests = json_object_get(root, "ingests");
  39. if (!ingests)
  40. goto finish;
  41. count = json_array_size(ingests);
  42. if (count <= 1 && cur_ingests.num)
  43. goto finish;
  44. free_ingests();
  45. for (size_t i = 0; i < count; i++) {
  46. json_t *item = json_array_get(ingests, i);
  47. json_t *item_name = json_object_get(item, "name");
  48. json_t *item_url = json_object_get(item, "url_template");
  49. struct ingest ingest = {0};
  50. struct dstr url = {0};
  51. if (!item_name || !item_url)
  52. continue;
  53. const char *url_str = json_string_value(item_url);
  54. const char *name_str = json_string_value(item_name);
  55. /* At the moment they currently mis-spell "deprecated",
  56. * but that may change in the future, so blacklist both */
  57. if (strstr(name_str, "deprecated") != NULL ||
  58. strstr(name_str, "depracated") != NULL)
  59. continue;
  60. dstr_copy(&url, url_str);
  61. dstr_replace(&url, "/{stream_key}", "");
  62. ingest.name = bstrdup(name_str);
  63. ingest.url = url.array;
  64. da_push_back(cur_ingests, &ingest);
  65. }
  66. if (!cur_ingests.num)
  67. goto finish;
  68. success = true;
  69. if (!write_file)
  70. goto finish;
  71. cache_old = obs_module_config_path("twitch_ingests.json");
  72. cache_new = obs_module_config_path("twitch_ingests.new.json");
  73. os_quick_write_utf8_file(cache_new, json, strlen(json), false);
  74. os_safe_replace(cache_old, cache_new, NULL);
  75. bfree(cache_old);
  76. bfree(cache_new);
  77. finish:
  78. if (root)
  79. json_decref(root);
  80. return success;
  81. }
  82. static bool twitch_ingest_update(void *param, struct file_download_data *data)
  83. {
  84. bool success;
  85. pthread_mutex_lock(&mutex);
  86. success = load_ingests((const char *)data->buffer.array, true);
  87. pthread_mutex_unlock(&mutex);
  88. if (success) {
  89. os_atomic_set_bool(&ingests_refreshed, true);
  90. os_atomic_set_bool(&ingests_loaded, true);
  91. }
  92. UNUSED_PARAMETER(param);
  93. return true;
  94. }
  95. void twitch_ingests_lock(void)
  96. {
  97. pthread_mutex_lock(&mutex);
  98. }
  99. void twitch_ingests_unlock(void)
  100. {
  101. pthread_mutex_unlock(&mutex);
  102. }
  103. size_t twitch_ingest_count(void)
  104. {
  105. return cur_ingests.num;
  106. }
  107. struct twitch_ingest twitch_ingest(size_t idx)
  108. {
  109. struct twitch_ingest ingest;
  110. if (cur_ingests.num <= idx) {
  111. ingest.name = NULL;
  112. ingest.url = NULL;
  113. } else {
  114. ingest = *(struct twitch_ingest *)(cur_ingests.array + idx);
  115. }
  116. return ingest;
  117. }
  118. void init_twitch_data(void)
  119. {
  120. da_init(cur_ingests);
  121. pthread_mutex_init(&mutex, NULL);
  122. }
  123. extern const char *get_module_name(void);
  124. void twitch_ingests_refresh(int seconds)
  125. {
  126. if (os_atomic_load_bool(&ingests_refreshed))
  127. return;
  128. if (!os_atomic_load_bool(&ingests_refreshing)) {
  129. os_atomic_set_bool(&ingests_refreshing, true);
  130. twitch_update_info = update_info_create_single(
  131. "[twitch ingest update] ", get_module_name(),
  132. "https://ingest.twitch.tv/ingests",
  133. twitch_ingest_update, NULL);
  134. }
  135. /* wait five seconds max when loading ingests for the first time */
  136. if (!os_atomic_load_bool(&ingests_loaded)) {
  137. for (int i = 0; i < seconds * 100; i++) {
  138. if (os_atomic_load_bool(&ingests_refreshed)) {
  139. break;
  140. }
  141. os_sleep_ms(10);
  142. }
  143. }
  144. }
  145. void load_twitch_data(void)
  146. {
  147. char *twitch_cache = obs_module_config_path("twitch_ingests.json");
  148. struct ingest def = {.name = bstrdup("Default"),
  149. .url = bstrdup("rtmp://live.twitch.tv/app")};
  150. pthread_mutex_lock(&mutex);
  151. da_push_back(cur_ingests, &def);
  152. pthread_mutex_unlock(&mutex);
  153. if (os_file_exists(twitch_cache)) {
  154. char *data = os_quick_read_utf8_file(twitch_cache);
  155. bool success;
  156. pthread_mutex_lock(&mutex);
  157. success = load_ingests(data, false);
  158. pthread_mutex_unlock(&mutex);
  159. if (success) {
  160. os_atomic_set_bool(&ingests_loaded, true);
  161. }
  162. bfree(data);
  163. }
  164. bfree(twitch_cache);
  165. }
  166. void unload_twitch_data(void)
  167. {
  168. update_info_destroy(twitch_update_info);
  169. free_ingests();
  170. pthread_mutex_destroy(&mutex);
  171. }