{"action":"created","comment":{"url":"https://api.github.com/repos/jp9000/obs-studio/pulls/comments/130226569","pull_request_review_id":53082043,"id":130226569,"diff_hunk":"@@ -0,0 +1,1188 @@\n+﻿/******************************************************************************\n+    Copyright (C) 2014 by Quinn Damerell <qdamere@microsoft.com>\n+\n+    This program is free software: you can redistribute it and/or modify\n+    it under the terms of the GNU General Public License as published by\n+    the Free Software Foundation, either version 2 of the License, or\n+    (at your option) any later version.\n+\n+    This program is distributed in the hope that it will be useful,\n+    but WITHOUT ANY WARRANTY; without even the implied warranty of\n+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+    GNU General Public License for more details.\n+\n+    You should have received a copy of the GNU General Public License\n+    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n+******************************************************************************/\n+\n+#include <obs-module.h>\n+#include <obs-avc.h>\n+#include <util/platform.h>\n+#include <util/circlebuf.h>\n+#include <util/dstr.h>\n+#include <util/threading.h>\n+#include <inttypes.h>\n+#include \"ftl.h\"\n+#include \"flv-mux.h\"\n+#include \"net-if.h\"\n+\n+#ifdef _WIN32\n+#include <Iphlpapi.h>\n+#else\n+#include <sys/ioctl.h>\n+#define INFINITE 0xFFFFFFFF\n+#endif\n+\n+#define do_log(level, format, ...) \\\n+\tblog(level, \"[ftl stream: '%s'] \" format, \\\n+\t\t\tobs_output_get_name(stream->output), ##__VA_ARGS__)\n+\n+#define warn(format, ...)  do_log(LOG_WARNING, format, ##__VA_ARGS__)\n+#define info(format, ...)  do_log(LOG_INFO,    format, ##__VA_ARGS__)\n+#define debug(format, ...) do_log(LOG_DEBUG,   format, ##__VA_ARGS__)\n+\n+#define OPT_DROP_THRESHOLD \"drop_threshold_ms\"\n+#define OPT_MAX_SHUTDOWN_TIME_SEC \"max_shutdown_time_sec\"\n+#define OPT_BIND_IP \"bind_ip\"\n+\n+typedef struct _nalu_t {\n+\tint len;\n+\tint dts_usec;\n+\tint send_marker_bit;\n+\tuint8_t *data;\n+} nalu_t;\n+\n+typedef struct _frame_of_nalus_t {\n+\tnalu_t nalus[100];\n+\tint total;\n+\tint complete_frame;\n+} frame_of_nalus_t;\n+\n+struct ftl_stream {\n+\tobs_output_t     *output;\n+\n+\tpthread_mutex_t  packets_mutex;\n+\tstruct circlebuf packets;\n+\tbool             sent_headers;\n+\tint64_t          frames_sent;\n+\n+\tvolatile bool    connecting;\n+\tpthread_t        connect_thread;\n+\tpthread_t        status_thread;\n+\n+\tvolatile bool    active;\n+\tvolatile bool    disconnected;\n+\tpthread_t        send_thread;\n+\n+\tint              max_shutdown_time_sec;\n+\n+\tos_sem_t         *send_sem;\n+\tos_event_t       *stop_event;\n+\tuint64_t         stop_ts;\n+\tuint64_t         shutdown_timeout_ts;\n+\n+\tstruct dstr      path;\n+\tuint32_t         channel_id;\n+\tstruct dstr      username, password;\n+\tstruct dstr      encoder_name;\n+\tstruct dstr      bind_ip;\n+\n+\t/* frame drop variables */\n+\tint64_t          drop_threshold_usec;\n+\tint64_t          pframe_drop_threshold_usec;\n+\tint              min_priority;\n+\tfloat            congestion;\n+\n+\tint64_t          last_dts_usec;\n+\n+\tuint64_t         total_bytes_sent;\n+    uint64_t         dropped_frames;\n+    uint64_t         last_nack_count;\n+\n+\tftl_handle_t\t    ftl_handle;\n+\tftl_ingest_params_t params;\n+\tint                 peak_kbps;\n+\tuint32_t            scale_width, scale_height, width, height;\n+\tframe_of_nalus_t    coded_pic_buffer;\n+};\n+\n+static void log_libftl_messages(ftl_log_severity_t log_level,\n+\t\tconst char *message);\n+static int init_connect(struct ftl_stream *stream);\n+static void *connect_thread(void *data);\n+static void *status_thread(void *data);\n+static int _ftl_error_to_obs_error(int status);\n+\n+static const char *ftl_stream_getname(void *unused)\n+{\n+\tUNUSED_PARAMETER(unused);\n+\treturn obs_module_text(\"FTLStream\");\n+}\n+\n+static void log_ftl(int level, const char *format, va_list args)\n+{\n+\tblogva(LOG_INFO, format, args);\n+}\n+\n+static inline size_t num_buffered_packets(struct ftl_stream *stream);\n+\n+static inline void free_packets(struct ftl_stream *stream)\n+{\n+\tsize_t num_packets;\n+\n+\tpthread_mutex_lock(&stream->packets_mutex);\n+\n+\tnum_packets = num_buffered_packets(stream);\n+\tif (num_packets)\n+\t\tinfo(\"Freeing %d remaining packets\", (int)num_packets);\n+\n+\twhile (stream->packets.size) {\n+\t\tstruct encoder_packet packet;\n+\t\tcirclebuf_pop_front(&stream->packets, &packet, sizeof(packet));\n+\t\tobs_encoder_packet_release(&packet);\n+\t}\n+\tpthread_mutex_unlock(&stream->packets_mutex);\n+}\n+\n+static inline bool stopping(struct ftl_stream *stream)\n+{\n+\treturn os_event_try(stream->stop_event) != EAGAIN;\n+}\n+\n+static inline bool connecting(struct ftl_stream *stream)\n+{\n+\treturn os_atomic_load_bool(&stream->connecting);\n+}\n+\n+static inline bool active(struct ftl_stream *stream)\n+{\n+\treturn os_atomic_load_bool(&stream->active);\n+}\n+\n+static inline bool disconnected(struct ftl_stream *stream)\n+{\n+\treturn os_atomic_load_bool(&stream->disconnected);\n+}\n+\n+static void ftl_stream_destroy(void *data)\n+{\n+\tstruct ftl_stream *stream = data;\n+\tftl_status_t status_code;\n+\n+\tinfo(\"ftl_stream_destroy\");\n+\n+\tif (stopping(stream) && !connecting(stream)) {\n+\t\tpthread_join(stream->send_thread, NULL);\n+\n+\t} else if (connecting(stream) || active(stream)) {\n+\t\tif (stream->connecting) {\n+\t\t\tinfo(\"wait for connect_thread to terminate\");\n+\t\t\tpthread_join(stream->status_thread, NULL);\n+\t\t\tpthread_join(stream->connect_thread, NULL);\n+\t\t\tinfo(\"wait for connect_thread to terminate: done\");\n+\t\t}\n+\n+\t\tstream->stop_ts = 0;\n+\t\tos_event_signal(stream->stop_event);\n+\n+\t\tif (active(stream)) {\n+\t\t\tos_sem_post(stream->send_sem);\n+\t\t\tobs_output_end_data_capture(stream->output);\n+\t\t\tpthread_join(stream->send_thread, NULL);\n+\t\t}\n+\t}\n+\n+\tinfo(\"ingest destroy\");\n+\n+\tstatus_code = ftl_ingest_destroy(&stream->ftl_handle);\n+\tif (status_code != FTL_SUCCESS) {\n+\t\tinfo(\"Failed to destroy from ingest %d\", status_code);\n+\t}\n+\n+\tif (stream) {\n+\t\tfree_packets(stream);\n+\t\tdstr_free(&stream->path);\n+\t\tdstr_free(&stream->username);\n+\t\tdstr_free(&stream->password);\n+\t\tdstr_free(&stream->encoder_name);\n+\t\tdstr_free(&stream->bind_ip);\n+\t\tos_event_destroy(stream->stop_event);\n+\t\tos_sem_destroy(stream->send_sem);\n+\t\tpthread_mutex_destroy(&stream->packets_mutex);\n+\t\tcirclebuf_free(&stream->packets);\n+\t\tbfree(stream);\n+\t}\n+}\n+\n+static void *ftl_stream_create(obs_data_t *settings, obs_output_t *output)\n+{\n+\tstruct ftl_stream *stream = bzalloc(sizeof(struct ftl_stream));\n+\tinfo(\"ftl_stream_create\");\n+\n+\tstream->output = output;\n+\tpthread_mutex_init_value(&stream->packets_mutex);\n+\n+\tstream->peak_kbps = -1;\n+\tftl_init();\n+\n+    if (pthread_mutex_init(&stream->packets_mutex, NULL) != 0) {\n+        goto fail;\n+    }\n+    if (os_event_init(&stream->stop_event, OS_EVENT_TYPE_MANUAL) != 0) {\n+        goto fail;\n+    }\n+\n+\tstream->coded_pic_buffer.total = 0;\n+\tstream->coded_pic_buffer.complete_frame = 0;\n+\n+\tUNUSED_PARAMETER(settings);\n+\treturn stream;\n+\n+fail:\n+\treturn NULL;\n+}\n+\n+static void ftl_stream_stop(void *data, uint64_t ts)\n+{\n+\tstruct ftl_stream *stream = data;\n+\tinfo(\"ftl_stream_stop\");\n+\n+    if (stopping(stream) && ts != 0) {\n+        return;\n+    }\n+\n+\tif (connecting(stream)) {\n+\t\tpthread_join(stream->status_thread, NULL);\n+\t\tpthread_join(stream->connect_thread, NULL);\n+\t}\n+\n+\tstream->stop_ts = ts / 1000ULL;\n+\tos_event_signal(stream->stop_event);\n+\n+    if (ts) {\n+        stream->shutdown_timeout_ts = ts +\n+            (uint64_t)stream->max_shutdown_time_sec * 1000000000ULL;\n+    }\n+\n+\tif (active(stream)) {\n+\t\tif (stream->stop_ts == 0)\n+\t\t\tos_sem_post(stream->send_sem);\n+\t}\n+}\n+\n+static inline bool get_next_packet(struct ftl_stream *stream,\n+\t\tstruct encoder_packet *packet)\n+{\n+\tbool new_packet = false;\n+\n+\tpthread_mutex_lock(&stream->packets_mutex);\n+\tif (stream->packets.size) {\n+\t\tcirclebuf_pop_front(&stream->packets, packet,\n+\t\t\t\tsizeof(struct encoder_packet));\n+\t\tnew_packet = true;\n+\t}\n+\tpthread_mutex_unlock(&stream->packets_mutex);\n+\n+\treturn new_packet;\n+}\n+\n+static int avc_get_video_frame(struct ftl_stream *stream,\n+\t\tstruct encoder_packet *packet, bool is_header, size_t idx)\n+{\n+\tint consumed = 0;\n+\tint len = (int)packet->size;\n+\tnalu_t *nalu;\n+\n+\tunsigned char *video_stream = packet->data;\n+\n+\twhile (consumed < packet->size) {\n+\t\tsize_t total_max = sizeof(stream->coded_pic_buffer.nalus) /\n+\t\t\tsizeof(stream->coded_pic_buffer.nalus[0]);\n+\n+\t\tif (stream->coded_pic_buffer.total >= total_max) {\n+\t\t\twarn(\"ERROR: cannot continue, nalu buffers are full\");\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tnalu = &stream->coded_pic_buffer.nalus\n+\t\t\t[stream->coded_pic_buffer.total];\n+\n+\t\tif (is_header) {\n+\t\t\tif (consumed == 0) {\n+\t\t\t\t//first 6 bytes are some obs header with part\n+\t\t\t\t//of the sps\n+\t\t\t\tvideo_stream += 6;\n+\t\t\t\tconsumed += 6;\n+\t\t\t} else {\n+\t\t\t\t//another spacer byte of 0x1\n+\t\t\t\tvideo_stream += 1;\n+\t\t\t\tconsumed += 1;\n+\t\t\t}\n+\n+\t\t\tlen = video_stream[0] << 8 | video_stream[1];\n+\t\t\tvideo_stream += 2;\n+\t\t\tconsumed += 2;\n+\t\t} else {\n+\t\t\tlen = video_stream[0] << 24 |\n+\t\t\t      video_stream[1] << 16 |\n+\t\t\t      video_stream[2] << 8 |\n+\t\t\t      video_stream[3];\n+\n+\t\t\tif (len > (packet->size - consumed)) {\n+\t\t\t\twarn(\"ERROR: got len of %d but packet only \"\n+\t\t\t\t\t\t\"has %d left\",\n+\t\t\t\t\t\tlen, packet->size - consumed);\n+\t\t\t}\n+\n+\t\t\tconsumed += 4;\n+\t\t\tvideo_stream += 4;\n+\t\t}\n+\n+\t\tconsumed += len;\n+\n+\t\tuint8_t nalu_type = video_stream[0] & 0x1F;\n+\t\tuint8_t nri = (video_stream[0] >> 5) & 0x3;\n+\n+\t\tint send_marker_bit = (consumed >= packet->size) && !is_header;\n+\n+\t\tif ((nalu_type != 12 && nalu_type != 6 && nalu_type != 9) ||\n+\t\t    nri) {\n+\t\t\tnalu->data = video_stream;\n+\t\t\tnalu->len = len;\n+\t\t\tnalu->send_marker_bit = 0;\n+\t\t\tstream->coded_pic_buffer.total++;\n+\t\t}\n+\n+\t\tvideo_stream += len;\n+\t}\n+\n+\tif (!is_header) {\n+\t\tsize_t idx = stream->coded_pic_buffer.total - 1;\n+\t\tstream->coded_pic_buffer.nalus[idx].send_marker_bit = 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int send_packet(struct ftl_stream *stream,\n+\t\tstruct encoder_packet *packet, bool is_header, size_t idx)\n+{\n+\tint bytes_sent = 0;\n+\tint recv_size = 0;\n+\tint ret = 0;\n+\n+\tif (packet->type == OBS_ENCODER_VIDEO) {\n+\t\tstream->coded_pic_buffer.total = 0;\n+\t\tavc_get_video_frame(stream, packet, is_header, idx);\n+\n+\t\tint i;\n+\t\tfor (i = 0; i < stream->coded_pic_buffer.total; i++) {\n+\t\t\tnalu_t *nalu = &stream->coded_pic_buffer.nalus[i];\n+\t\t\tbytes_sent += ftl_ingest_send_media_dts(\n+\t\t\t\t\t&stream->ftl_handle,\n+\t\t\t\t\tFTL_VIDEO_DATA,\n+\t\t\t\t\tpacket->dts_usec,\n+\t\t\t\t\tnalu->data,\n+\t\t\t\t\tnalu->len,\n+\t\t\t\t\tnalu->send_marker_bit);\n+\n+\t\t\tif (nalu->send_marker_bit) {\n+\t\t\t\tstream->frames_sent++;\n+\t\t\t}\n+\t\t}\n+\n+\t} else if (packet->type == OBS_ENCODER_AUDIO) {\n+\t\tbytes_sent += ftl_ingest_send_media_dts(\n+\t\t\t\t&stream->ftl_handle,\n+\t\t\t\tFTL_AUDIO_DATA,\n+\t\t\t\tpacket->dts_usec,\n+\t\t\t\tpacket->data,\n+\t\t\t\t(int)packet->size, 0);\n+\t} else {\n+\t\twarn(\"Got packet type %d\", packet->type);\n+\t}\n+\n+    if (is_header) {\n+        bfree(packet->data);\n+    }\n+    else {","path":"plugins/obs-outputs/ftl-stream.c","position":null,"original_position":408,"commit_id":"f26dadae567998c7164a11c5d0b90f1a851605b8","original_commit_id":"d1911fa2905e9499f378637f2971ba2ddadadfce","user":{"login":"QuinnDamerell-MS","id":21225051,"avatar_url":"https://avatars3.githubusercontent.com/u/21225051?v=4","gravatar_id":"","url":"https://api.github.com/users/QuinnDamerell-MS","html_url":"https://github.com/QuinnDamerell-MS","followers_url":"https://api.github.com/users/QuinnDamerell-MS/followers","following_url":"https://api.github.com/users/QuinnDamerell-MS/following{/other_user}","gists_url":"https://api.github.com/users/QuinnDamerell-MS/gists{/gist_id}","starred_url":"https://api.github.com/users/QuinnDamerell-MS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/QuinnDamerell-MS/subscriptions","organizations_url":"https://api.github.com/users/QuinnDamerell-MS/orgs","repos_url":"https://api.github.com/users/QuinnDamerell-MS/repos","events_url":"https://api.github.com/users/QuinnDamerell-MS/events{/privacy}","received_events_url":"https://api.github.com/users/QuinnDamerell-MS/received_events","type":"User","site_admin":false},"body":"Fixed.","created_at":"2017-07-29T19:14:00Z","updated_at":"2017-07-29T19:14:00Z","html_url":"https://github.com/jp9000/obs-studio/pull/980#discussion_r130226569","pull_request_url":"https://api.github.com/repos/jp9000/obs-studio/pulls/980","_links":{"self":{"href":"https://api.github.com/repos/jp9000/obs-studio/pulls/comments/130226569"},"html":{"href":"https://github.com/jp9000/obs-studio/pull/980#discussion_r130226569"},"pull_request":{"href":"https://api.github.com/repos/jp9000/obs-studio/pulls/980"}}},"pull_request":{"url":"https://api.github.com/repos/jp9000/obs-studio/pulls/980","id":133039685,"html_url":"https://github.com/jp9000/obs-studio/pull/980","diff_url":"https://github.com/jp9000/obs-studio/pull/980.diff","patch_url":"https://github.com/jp9000/obs-studio/pull/980.patch","issue_url":"https://api.github.com/repos/jp9000/obs-studio/issues/980","number":980,"state":"open","locked":false,"title":"obs-outputs: Add Mixer FTL support","user":{"login":"QuinnDamerell-MS","id":21225051,"avatar_url":"https://avatars3.githubusercontent.com/u/21225051?v=4","gravatar_id":"","url":"https://api.github.com/users/QuinnDamerell-MS","html_url":"https://github.com/QuinnDamerell-MS","followers_url":"https://api.github.com/users/QuinnDamerell-MS/followers","following_url":"https://api.github.com/users/QuinnDamerell-MS/following{/other_user}","gists_url":"https://api.github.com/users/QuinnDamerell-MS/gists{/gist_id}","starred_url":"https://api.github.com/users/QuinnDamerell-MS/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/QuinnDamerell-MS/subscriptions","organizations_url":"https://api.github.com/users/QuinnDamerell-MS/orgs","repos_url":"https://api.github.com/users/QuinnDamerell-MS/repos","events_url":"https://api.github.com/users/QuinnDamerell-MS/events{/privacy}","received_events_url":"https://api.github.com/users/QuinnDamerell-MS/received_events","type":"User","site_admin":false},"body":"This work adds support for Mixer’s custom FTL protocol to OBS. This includes adding support for encoding OPUS audio, adding the FTL submodule, and adding a ftl-output.","created_at":"2017-07-28T19:48:30Z","updated_at":"2017-07-29T19:14:00Z","closed_at":null,"merged_at":null,"merge_commit_sha":"005e3f9f273223a360f2d5f8b5004ecfe08b95c7","assignee":null,"assignees":[],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/jp9000/obs-studio/pulls/980/commits","review_comments_url":"https://api.github.com/repos/jp9000/obs-studio/pulls/980/comments","review_comment_url":"https://api.github.com/repos/jp9000/obs-studio/pulls/comments{/number}","comments_url":"https://api.github.com/repos/jp9000/obs-studio/issues/980/comments","statuses_url":"https://api.github.com/repos/jp9000/obs-studio/statuses/f26dadae567998c7164a11c5d0b90f1a851605b8","head":{"label":"mixer:ftl-support","ref":"ftl-support","sha":"f26dadae567998c7164a11c5d0b90f1a851605b8","user":{"login":"mixer","id":11798804,"avatar_url":"https://avatars3.githubusercontent.com/u/11798804?v=4","gravatar_id":"","url":"https://api.github.com/users/mixer","html_url":"https://github.com/mixer","followers_url":"https://api.github.com/users/mixer/followers","following_url":"https://api.github.com/users/mixer/following{/other_user}","gists_url":"https://api.github.com/users/mixer/gists{/gist_id}","starred_url":"https://api.github.com/users/mixer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mixer/subscriptions","organizations_url":"https://api.github.com/users/mixer/orgs","repos_url":"https://api.github.com/users/mixer/repos","events_url":"https://api.github.com/users/mixer/events{/privacy}","received_events_url":"https://api.github.com/users/mixer/received_events","type":"Organization","site_admin":false},"repo":{"id":42902103,"name":"obs-studio-ftl","full_name":"mixer/obs-studio-ftl","owner":{"login":"mixer","id":11798804,"avatar_url":"https://avatars3.githubusercontent.com/u/11798804?v=4","gravatar_id":"","url":"https://api.github.com/users/mixer","html_url":"https://github.com/mixer","followers_url":"https://api.github.com/users/mixer/followers","following_url":"https://api.github.com/users/mixer/following{/other_user}","gists_url":"https://api.github.com/users/mixer/gists{/gist_id}","starred_url":"https://api.github.com/users/mixer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mixer/subscriptions","organizations_url":"https://api.github.com/users/mixer/orgs","repos_url":"https://api.github.com/users/mixer/repos","events_url":"https://api.github.com/users/mixer/events{/privacy}","received_events_url":"https://api.github.com/users/mixer/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mixer/obs-studio-ftl","description":"OBS Studio with FTL","fork":true,"url":"https://api.github.com/repos/mixer/obs-studio-ftl","forks_url":"https://api.github.com/repos/mixer/obs-studio-ftl/forks","keys_url":"https://api.github.com/repos/mixer/obs-studio-ftl/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mixer/obs-studio-ftl/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mixer/obs-studio-ftl/teams","hooks_url":"https://api.github.com/repos/mixer/obs-studio-ftl/hooks","issue_events_url":"https://api.github.com/repos/mixer/obs-studio-ftl/issues/events{/number}","events_url":"https://api.github.com/repos/mixer/obs-studio-ftl/events","assignees_url":"https://api.github.com/repos/mixer/obs-studio-ftl/assignees{/user}","branches_url":"https://api.github.com/repos/mixer/obs-studio-ftl/branches{/branch}","tags_url":"https://api.github.com/repos/mixer/obs-studio-ftl/tags","blobs_url":"https://api.github.com/repos/mixer/obs-studio-ftl/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mixer/obs-studio-ftl/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mixer/obs-studio-ftl/git/refs{/sha}","trees_url":"https://api.github.com/repos/mixer/obs-studio-ftl/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mixer/obs-studio-ftl/statuses/{sha}","languages_url":"https://api.github.com/repos/mixer/obs-studio-ftl/languages","stargazers_url":"https://api.github.com/repos/mixer/obs-studio-ftl/stargazers","contributors_url":"https://api.github.com/repos/mixer/obs-studio-ftl/contributors","subscribers_url":"https://api.github.com/repos/mixer/obs-studio-ftl/subscribers","subscription_url":"https://api.github.com/repos/mixer/obs-studio-ftl/subscription","commits_url":"https://api.github.com/repos/mixer/obs-studio-ftl/commits{/sha}","git_commits_url":"https://api.github.com/repos/mixer/obs-studio-ftl/git/commits{/sha}","comments_url":"https://api.github.com/repos/mixer/obs-studio-ftl/comments{/number}","issue_comment_url":"https://api.github.com/repos/mixer/obs-studio-ftl/issues/comments{/number}","contents_url":"https://api.github.com/repos/mixer/obs-studio-ftl/contents/{+path}","compare_url":"https://api.github.com/repos/mixer/obs-studio-ftl/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mixer/obs-studio-ftl/merges","archive_url":"https://api.github.com/repos/mixer/obs-studio-ftl/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mixer/obs-studio-ftl/downloads","issues_url":"https://api.github.com/repos/mixer/obs-studio-ftl/issues{/number}","pulls_url":"https://api.github.com/repos/mixer/obs-studio-ftl/pulls{/number}","milestones_url":"https://api.github.com/repos/mixer/obs-studio-ftl/milestones{/number}","notifications_url":"https://api.github.com/repos/mixer/obs-studio-ftl/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mixer/obs-studio-ftl/labels{/name}","releases_url":"https://api.github.com/repos/mixer/obs-studio-ftl/releases{/id}","deployments_url":"https://api.github.com/repos/mixer/obs-studio-ftl/deployments","created_at":"2015-09-22T00:50:32Z","updated_at":"2017-07-28T15:18:40Z","pushed_at":"2017-07-29T19:10:28Z","git_url":"git://github.com/mixer/obs-studio-ftl.git","ssh_url":"git@github.com:mixer/obs-studio-ftl.git","clone_url":"https://github.com/mixer/obs-studio-ftl.git","svn_url":"https://github.com/mixer/obs-studio-ftl","homepage":"","size":23039,"stargazers_count":97,"watchers_count":97,"language":"C","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":18,"mirror_url":null,"open_issues_count":48,"forks":18,"open_issues":48,"watchers":97,"default_branch":"ftl"}},"base":{"label":"jp9000:master","ref":"master","sha":"8b68ccdc139528b440dd83b312191d64fdc5ea6e","user":{"login":"jp9000","id":2084112,"avatar_url":"https://avatars1.githubusercontent.com/u/2084112?v=4","gravatar_id":"","url":"https://api.github.com/users/jp9000","html_url":"https://github.com/jp9000","followers_url":"https://api.github.com/users/jp9000/followers","following_url":"https://api.github.com/users/jp9000/following{/other_user}","gists_url":"https://api.github.com/users/jp9000/gists{/gist_id}","starred_url":"https://api.github.com/users/jp9000/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jp9000/subscriptions","organizations_url":"https://api.github.com/users/jp9000/orgs","repos_url":"https://api.github.com/users/jp9000/repos","events_url":"https://api.github.com/users/jp9000/events{/privacy}","received_events_url":"https://api.github.com/users/jp9000/received_events","type":"User","site_admin":false},"repo":{"id":13233158,"name":"obs-studio","full_name":"jp9000/obs-studio","owner":{"login":"jp9000","id":2084112,"avatar_url":"https://avatars1.githubusercontent.com/u/2084112?v=4","gravatar_id":"","url":"https://api.github.com/users/jp9000","html_url":"https://github.com/jp9000","followers_url":"https://api.github.com/users/jp9000/followers","following_url":"https://api.github.com/users/jp9000/following{/other_user}","gists_url":"https://api.github.com/users/jp9000/gists{/gist_id}","starred_url":"https://api.github.com/users/jp9000/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jp9000/subscriptions","organizations_url":"https://api.github.com/users/jp9000/orgs","repos_url":"https://api.github.com/users/jp9000/repos","events_url":"https://api.github.com/users/jp9000/events{/privacy}","received_events_url":"https://api.github.com/users/jp9000/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jp9000/obs-studio","description":"OBS - Free and open source software for live streaming and screen recording","fork":false,"url":"https://api.github.com/repos/jp9000/obs-studio","forks_url":"https://api.github.com/repos/jp9000/obs-studio/forks","keys_url":"https://api.github.com/repos/jp9000/obs-studio/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jp9000/obs-studio/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jp9000/obs-studio/teams","hooks_url":"https://api.github.com/repos/jp9000/obs-studio/hooks","issue_events_url":"https://api.github.com/repos/jp9000/obs-studio/issues/events{/number}","events_url":"https://api.github.com/repos/jp9000/obs-studio/events","assignees_url":"https://api.github.com/repos/jp9000/obs-studio/assignees{/user}","branches_url":"https://api.github.com/repos/jp9000/obs-studio/branches{/branch}","tags_url":"https://api.github.com/repos/jp9000/obs-studio/tags","blobs_url":"https://api.github.com/repos/jp9000/obs-studio/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jp9000/obs-studio/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jp9000/obs-studio/git/refs{/sha}","trees_url":"https://api.github.com/repos/jp9000/obs-studio/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jp9000/obs-studio/statuses/{sha}","languages_url":"https://api.github.com/repos/jp9000/obs-studio/languages","stargazers_url":"https://api.github.com/repos/jp9000/obs-studio/stargazers","contributors_url":"https://api.github.com/repos/jp9000/obs-studio/contributors","subscribers_url":"https://api.github.com/repos/jp9000/obs-studio/subscribers","subscription_url":"https://api.github.com/repos/jp9000/obs-studio/subscription","commits_url":"https://api.github.com/repos/jp9000/obs-studio/commits{/sha}","git_commits_url":"https://api.github.com/repos/jp9000/obs-studio/git/commits{/sha}","comments_url":"https://api.github.com/repos/jp9000/obs-studio/comments{/number}","issue_comment_url":"https://api.github.com/repos/jp9000/obs-studio/issues/comments{/number}","contents_url":"https://api.github.com/repos/jp9000/obs-studio/contents/{+path}","compare_url":"https://api.github.com/repos/jp9000/obs-studio/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jp9000/obs-studio/merges","archive_url":"https://api.github.com/repos/jp9000/obs-studio/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jp9000/obs-studio/downloads","issues_url":"https://api.github.com/repos/jp9000/obs-studio/issues{/number}","pulls_url":"https://api.github.com/repos/jp9000/obs-studio/pulls{/number}","milestones_url":"https://api.github.com/repos/jp9000/obs-studio/milestones{/number}","notifications_url":"https://api.github.com/repos/jp9000/obs-studio/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jp9000/obs-studio/labels{/name}","releases_url":"https://api.github.com/repos/jp9000/obs-studio/releases{/id}","deployments_url":"https://api.github.com/repos/jp9000/obs-studio/deployments","created_at":"2013-10-01T02:40:31Z","updated_at":"2017-07-29T15:47:58Z","pushed_at":"2017-07-29T19:10:30Z","git_url":"git://github.com/jp9000/obs-studio.git","ssh_url":"git@github.com:jp9000/obs-studio.git","clone_url":"https://github.com/jp9000/obs-studio.git","svn_url":"https://github.com/jp9000/obs-studio","homepage":"https://obsproject.com/mantis/","size":22752,"stargazers_count":6782,"watchers_count":6782,"language":"C","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1618,"mirror_url":null,"open_issues_count":58,"forks":1618,"open_issues":58,"watchers":6782,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/jp9000/obs-studio/pulls/980"},"html":{"href":"https://github.com/jp9000/obs-studio/pull/980"},"issue":{"href":"https://api.github.com/repos/jp9000/obs-studio/issues/980"},"comments":{"href":"https://api.github.com/repos/jp9000/obs-studio/issues/980/comments"},"review_comments":{"href":"https://api.github.com/repos/jp9000/obs-studio/pulls/980/comments"},"review_comment":{"href":"https://api.github.com/repos/jp9000/obs-studio/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/jp9000/obs-studio/pulls/980/commits"},"statuses":{"href":"https://api.github.com/repos/jp9000/obs-studio/statuses/f26dadae567998c7164a11c5d0b90f1a851605b8"}}}}
{"actor":{"display_login":"QuinnDamerell-MS"}}