Categories
cmake code graphics gui qt Stream video

URL/API Source OBS Plugin: Fetch Live Data in your Stream

Walk through the source code for the URL API source plugin for OBS, built in C++, which allows users to fetch information from a URL and parse the output, which can then be rendered on screen.

If you’re a fan of OBS (Open Broadcaster Software), you may already be familiar with its vast library of plugins that enhance its functionality and provide added features. One such plugin that I recently developed is the URL API source plugin. This plugin allows you to fetch information from a URL and display it in your OBS stream. In this blog post, we will take a closer look at the source code for this plugin and understand how it works.

To begin with, let’s quickly recap what the URL API source plugin does. When set up as a new source in OBS, it has a few properties, including the URL from which it fetches information, the parsing of the output, styling using HTML tags and CSS, and a timer. The plugin is built in C++ and consists of various functions that handle tasks like making HTTP requests, parsing the response, and rendering the text on screen.

The core functionality of the URL API source plugin lies in a thread that runs continuously in a loop, separate from the rendering thread and the rest of OBS. This thread periodically sends the HTTP request, parses the output, and sends it back to OBS for rendering on screen. By running the requests on a timer, the plugin ensures that the rendering pipeline remains lean, as it doesn’t have to handle the requests frame by frame.

The thread function:

void curl_loop(struct url_source_data *usd)
{
  struct obs_source_frame frame = {};

  while (true) {
    {
      std::lock_guard<std::mutex> lock(*(usd->curl_mutex.get()));
      if (!usd->curl_thread_run) {
        break;
      }
    }

    // Send the request
    struct request_data_handler_response response =
      request_data_handler(&(usd->request_data));
    if (response.status_code != 200) {
      obs_log(LOG_INFO, "Failed to send request");
    } else {
      uint32_t width = 0;
      uint32_t height = 0;
      uint8_t *renderBuffer = nullptr;

      // prepare the text from the template
      std::string text = usd->output_text_template;
      // if the template is empty use the response body
      if (text.empty()) {
        text = response.body_parsed;
      } else {
        // attempt to replace {output} with the response body
        text = std::regex_replace(text, std::regex("\\{output\\}"), response.body_parsed);
      }
      // render the text
      render_text_with_qtextdocument(text, width, height, &renderBuffer, usd->css_props);
      // Update the frame
      frame.data[0] = renderBuffer;
      frame.linesize[0] = width * 4;
      frame.width = width;
      frame.height = height;
      frame.format = VIDEO_FORMAT_BGRA;

      // Send the frame
      obs_source_output_video(usd->source, &frame);

      // Free the render buffer
      bfree(renderBuffer);
    }

    const int64_t sleep_time_ms = (int64_t)(usd->update_timer_ms);
    {
      std::unique_lock<std::mutex> lock(*(usd->curl_mutex.get()));
      // Sleep for n ns as per the update timer for the remaining time
      usd->curl_thread_cv->wait_for(lock, std::chrono::milliseconds(sleep_time_ms));
    }
  }
  obs_log(LOG_INFO, "Stopping URL Source thread");
}

One interesting feature of the plugin is the use of a condition variable (curl_thread_cv above) for the thread’s sleep function. This allows the thread to be interrupted if the source is hidden or destroyed, preventing OBS from hanging until the thread completes its sleep cycle

The plugin supports various options for parsing the output, including JSON, XML, and regular expressions. For JSON parsing, the developer has implemented the Nlohmann JSON parser and JSON pointer for extracting specific information from the response. Similarly, XML parsing is handled using the PugiXML library and XPath for extraction. And for regular expression parsing, the plugin utilizes std::regex library.

Once the request has been completed successfully, the plugin renders the fetched information on screen using the Qt library’s QtTextDocument. This allows for typesetting or layout of the text using HTML or markdown. By creating a template with the desired styling and replacing the text content with the fetched information, the plugin achieves a user-friendly rendering of the data.

The text rendering function:

void render_text_with_qtextdocument(const std::string &text, uint32_t &width, uint32_t &height,
				    uint8_t **data, const std::string &css_props)
{
  // apply response in template
  QString html = QString(template_text).replace("{text}", QString::fromStdString(text)).replace("{css_props}", QString::fromStdString(css_props));
  QTextDocument textDocument;
  textDocument.setHtml(html);
  textDocument.setTextWidth(640);

  QPixmap pixmap(textDocument.size().toSize());
  pixmap.fill(Qt::transparent);
  QPainter painter;
  painter.begin(&pixmap);
  painter.setCompositionMode(QPainter::CompositionMode_Source);

  // render text
  textDocument.drawContents(&painter);

	  painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
  painter.end();

  // save pixmap to buffer
  QImage image = pixmap.toImage();
  // crop to the idealWidth of the text
  image = image.copy(0, 0, (int)textDocument.idealWidth(), image.height());
  // get width and height
  width = image.width();
  height = image.height();
  // allocate output buffer (RGBA), user must free
  *data = (uint8_t *)bzalloc(width * height * 4);
  // copy image data to output buffer
  memcpy(*data, image.bits(), width * height * 4);
}

Overall, the URL API source plugin for OBS is a simple yet powerful tool for fetching and displaying dynamic content in your OBS stream. The elegance of its implementation lies in the careful handling of HTTP requests, parsing of various data formats, and the efficient integration with OBS’s rendering pipeline.

If you’re interested in diving deeper into the code and exploring the additional features of the plugin, I highly recommend taking a look at the source code yourself. The developer has put in significant effort to ensure the plugin’s functionality and ease of use. By studying the code, you can gain insights into the inner workings of the plugin and potentially even contribute to its development.

In conclusion, the URL API source plugin for OBS is a valuable addition to any streaming setup, providing a seamless way to fetch and display external information in your live streams. Its well-structured source code, efficient threading, and support for various data formats make it a versatile tool for streamers. So, give it a try and see how it enhances your streaming experience.