<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: AkiraFukushima</title>
    <description>The latest articles on DEV Community by AkiraFukushima (@h3poteto).</description>
    <link>https://dev.to/h3poteto</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1011500%2F6bf4de1a-adc6-4f6f-8d72-7737c710edba.png</url>
      <title>DEV Community: AkiraFukushima</title>
      <link>https://dev.to/h3poteto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/h3poteto"/>
    <language>en</language>
    <item>
      <title>Rheomesh now supports Recordings</title>
      <dc:creator>AkiraFukushima</dc:creator>
      <pubDate>Wed, 23 Jul 2025 03:43:36 +0000</pubDate>
      <link>https://dev.to/h3poteto/rheomesh-now-supports-recordings-1ndl</link>
      <guid>https://dev.to/h3poteto/rheomesh-now-supports-recordings-1ndl</guid>
      <description>&lt;p&gt;Once I posted about my WebRTC SFU library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/h3poteto/developing-a-webrtc-sfu-library-in-rust-2g8p"&gt;Developing a WebRTC SFU library in Rust &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve recently added a recording function to this project. This update brings new capabilities for capturing and processing RTP streams in flexible, server-friendly manner.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/h3poteto/rheomesh/releases/tag/v0.5.0" rel="noopener noreferrer"&gt;v0.5.0 release&lt;/a&gt; introduces recording features.&lt;/p&gt;

&lt;p&gt;And I published the documentation site. The recording feature is explained on the site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://h3poteto.github.io/rheomesh//pages/04_recording/" rel="noopener noreferrer"&gt;https://h3poteto.github.io/rheomesh//pages/04_recording/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Philosophy: Raw RTP Packet Forwarding
&lt;/h2&gt;

&lt;p&gt;Rather than creating a monolithic function that handles everything from capture to file generation, I opted for a more modular approach. The recording functionality simply forwards raw RTP packets, leaving encoding and file creation to external tools like FFmpeg or GStreamer.&lt;/p&gt;

&lt;p&gt;This design decision prioritizes flexibility, especially for server deployments where different use cases might require different encoding strategies. Both FFmpeg and GStreamer can generate video files from raw RTP packets, making this delegation approach both practical and powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  SDP Generation
&lt;/h2&gt;

&lt;p&gt;While the core functionality focuses on packet forwarding, I recognized that Session Description Protocol (SDP) information would be essential for most recording workflows. The library includes SDP generation capabilities, particularly important for FFmpeg-based recording setups.&lt;/p&gt;

&lt;p&gt;You can generate SDP strings using the &lt;a href="https://docs.rs/rheomesh/latest/rheomesh/recording/recording_transport/struct.RecordingTransport.html#method.generate_sdp" rel="noopener noreferrer"&gt;generate_sdp&lt;/a&gt; method from the &lt;a href="https://docs.rs/rheomesh/latest/rheomesh/recording/recording_transport/struct.RecordingTransport.html" rel="noopener noreferrer"&gt;RecordingTransport&lt;/a&gt; struct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recording Workflow Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using FFmpeg
&lt;/h3&gt;

&lt;p&gt;To start recording with FFmpeg, first generate the SDP file and set up FFmpeg to wait for incoming streams:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ffplay &lt;span class="nt"&gt;-protocol_whitelist&lt;/span&gt; file,rtp,udp &lt;span class="nt"&gt;-analyzeduration&lt;/span&gt; 10000000 &lt;span class="nt"&gt;-probesize&lt;/span&gt; 50000000 &lt;span class="nt"&gt;-f&lt;/span&gt; sdp &lt;span class="nt"&gt;-i&lt;/span&gt; stream.sdp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, initiate recording using the start_recording method. This begins raw RTP packet transmission, and you should see the video stream in FFplay.&lt;/p&gt;

&lt;p&gt;To save the recording to a file instead of just viewing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ffmpeg &lt;span class="nt"&gt;-protocol_whitelist&lt;/span&gt; file,rtp,udp &lt;span class="nt"&gt;-i&lt;/span&gt; stream.sdp &lt;span class="nt"&gt;-c&lt;/span&gt; copy output.mkv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will record the stream directly to &lt;code&gt;output.mkv&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using GStreamer
&lt;/h3&gt;

&lt;p&gt;For GStreamer users, you’ll need to parse the SDP information and construct an appropriate pipeline. Here’s an example command for H.264 streams:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gst-launch-1.0 udpsrc &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;30001 &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  application/x-rtp,payload&lt;span class="o"&gt;=&lt;/span&gt;103,encoding-name&lt;span class="o"&gt;=&lt;/span&gt;H264 &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  rtph264depay &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  h264parse &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  avdec_h264 &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  videoconvert &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  autovideosink
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The specific parameters will vary depending on your encoding format. For detailed information about RTP and parser elements, consult the official GStreamer documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gstreamer.freedesktop.org/documentation/rtp/index.html?gi-language=c" rel="noopener noreferrer"&gt;RTP Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gstreamer.freedesktop.org/documentation/videoparsersbad/index.html?gi-language=c" rel="noopener noreferrer"&gt;Video Parsers Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make sure your pipeline parameters match the encoding specified in the generated SDP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The project includes practical examples to help you get started. Check out the &lt;a href="https://h3poteto.github.io/rheomesh//others/examples/" rel="noopener noreferrer"&gt;examples section&lt;/a&gt; in the documentation.&lt;/p&gt;

&lt;p&gt;The camera example demonstrates the recording functionality in action. It displays the generated SDP on screen, which you can copy and use with FFmpeg or GStreamer to receive and process the stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;Looking ahead, I plan to focus on improving the relay functionality to make the library even more robust and feature-complete.&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>sfu</category>
      <category>recording</category>
      <category>rust</category>
    </item>
    <item>
      <title>Developing a WebRTC SFU library in Rust</title>
      <dc:creator>AkiraFukushima</dc:creator>
      <pubDate>Wed, 26 Mar 2025 16:18:10 +0000</pubDate>
      <link>https://dev.to/h3poteto/developing-a-webrtc-sfu-library-in-rust-2g8p</link>
      <guid>https://dev.to/h3poteto/developing-a-webrtc-sfu-library-in-rust-2g8p</guid>
      <description>&lt;p&gt;If you're working with WebRTC, you've probably heard of Selective Forwarding Units (SFUs). There are solid open-source SFUs out there- &lt;a href="https://github.com/versatica/mediasoup" rel="noopener noreferrer"&gt;mediasoup&lt;/a&gt;, &lt;a href="https://github.com/livekit/livekit" rel="noopener noreferrer"&gt;livekit&lt;/a&gt;, and &lt;a href="https://github.com/jitsi" rel="noopener noreferrer"&gt;Jitsi&lt;/a&gt; to name a few.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;There is a WebRTC protocol implementation in Rust; it is &lt;a href="https://github.com/webrtc-rs/webrtc" rel="noopener noreferrer"&gt;webrtc-rs&lt;/a&gt;. This repository provides some examples that include SFU, but we still need to write a lot of code to implement a practical SFU server. In addition, existing repositories implementing SFU servers with webrtc-rs weren't directly usable for my needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Rheomesh: A Rust SFU SDK
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/h3poteto/rheomesh" rel="noopener noreferrer"&gt;https://github.com/h3poteto/rheomesh&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I developed a new SFU library in Rust to build your own WebRTC SFU servers. A key design principle was separating SFU-related logic from signaling protocols. Usually, you need a signaling logic to connect client browsers to your SFU server. I often use WebSocket as the signaling protocol, but, of course, you can choose other protocols, like gRPC or MQTT. &lt;/p&gt;

&lt;h2&gt;
  
  
  Key features
&lt;/h2&gt;

&lt;p&gt;Key features of this library are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rust SDKs for SFU server development&lt;/li&gt;
&lt;li&gt;JavaScript library to communicate with the SFU server&lt;/li&gt;
&lt;li&gt;Supporting relay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Audio and video streaming&lt;/li&gt;
&lt;li&gt;DataChannel&lt;/li&gt;
&lt;li&gt;Simulcast&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nowadays, there is Scalable Video Coding (SVC), but it is not supported yet. Currently, Rheomesh supports only temporal layer filtering and doesn't support spatial layer filtering due to patent concerns.&lt;/p&gt;

&lt;p&gt;An interesting technical challenge during development was implementing a &lt;a href="https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension" rel="noopener noreferrer"&gt;Dependency Descriptor&lt;/a&gt; parser. While webrtc-rs provides temporal layer parsing for VP8 and VP9, it lacks support for AV1 and H264, which use Dependency Descriptors for temporal and spatial layer representation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;This library doesn't contain WebSocket server, so please implement a WebSocket server at first, and call Rheomesh methods from it. &lt;/p&gt;

&lt;h3&gt;
  
  
  Server-side
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/h3poteto/rheomesh/blob/master/sfu/README.md" rel="noopener noreferrer"&gt;https://github.com/h3poteto/rheomesh/blob/master/sfu/README.md&lt;/a&gt;&lt;br&gt;
First of all, please create a worker and router. Router accommodates multiple transports, and they can communicate with each other. That means transports belonging to the same Router can send/receive their media. Router is like a meeting room.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;rheomesh&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;config&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MediaConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;rheomesh&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;MediaConfig&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;WebRTCTransportConfig&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="py"&gt;.configuration.ice_servers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;RTCIceServer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"stun:stun.l.google.com:19302"&lt;/span&gt;&lt;span class="nf"&gt;.to_owned&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="nn"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}];&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;publish_transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="nf"&gt;.create_publish_transport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;subscribe_transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="nf"&gt;.create_subscribe_transport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please call Rheomesh methods in your WebSocket handler.&lt;/p&gt;

&lt;p&gt;Regarding publishers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;publish_transport&lt;/span&gt;
  &lt;span class="nf"&gt;.on_ice_candidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="nf"&gt;.to_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to parse candidate"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// Send `init` message to the client. The client has to call `addIceCandidate` method with this parameter.&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// When you receive RTCIceCandidateInit message from the client.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;publish_transport&lt;/span&gt;
  &lt;span class="nf"&gt;.add_ice_candidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;.await&lt;/span&gt;
  &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to add ICE candidate"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// When you receive offer message from the client.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;publish_transport&lt;/span&gt;
  &lt;span class="nf"&gt;.get_answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;.await&lt;/span&gt;
  &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to connect publish_transport"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Send `answer` message to the client. The client has to call `setAnswer` method.&lt;/span&gt;

&lt;span class="c1"&gt;// Publish&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;publish_transport&lt;/span&gt;&lt;span class="nf"&gt;.publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;track_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regarding subscribers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;subscribe_transport&lt;/span&gt;
  &lt;span class="nf"&gt;.on_ice_candidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="nf"&gt;.to_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to parse candidate"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// Send `init` message to the client. The client has to call `addIceCandidate` method with this parameter.&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;subscribe_transport&lt;/span&gt;
  &lt;span class="nf"&gt;.on_negotiation_needed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Send `offer` message to the client. The client has to call `setOffer` method.&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="c1"&gt;// When you receive RTCIceCandidateInit message from the client.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subscribe_transport&lt;/span&gt;
  &lt;span class="nf"&gt;.add_ice_candidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;.await&lt;/span&gt;
  &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to add ICE candidate"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Subscribe&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subscribe_transport&lt;/span&gt;
  &lt;span class="nf"&gt;.subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;track_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;.await&lt;/span&gt;
  &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to connect subscribe_transport"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Send `offer` message to the client. The client has to call `setOffer` method.&lt;/span&gt;

&lt;span class="c1"&gt;// When you receive answer message from the client.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subscribe_transport&lt;/span&gt;
  &lt;span class="nf"&gt;.set_answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;.await&lt;/span&gt;
  &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to set answer"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Client-side
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/h3poteto/rheomesh/blob/master/client/README.md" rel="noopener noreferrer"&gt;https://github.com/h3poteto/rheomesh/blob/master/client/README.md&lt;/a&gt;&lt;br&gt;
At first, please initialize transports.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PublishTransport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rheomesh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;peerConnectionConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RTCConfiguration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;iceServers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stun:stun.l.google.com:19302&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publishTransport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PublishTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;peerConnectionConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscribeTransport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SubscribeTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;peerConnectionConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regarding publishers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;publishTransport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;icecandidate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Send `candidate` to the server. The server has to call `add_ice_candidate` method with this parameter.&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// When you receive candidateInit message from the server.&lt;/span&gt;
&lt;span class="nx"&gt;publishTransport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addIceCandidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidateInit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Publish a track.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaDevices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDisplayMedia&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;publishTransport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Send `offer` to the server. The server has to call `get_answer` method with this parameter.&lt;/span&gt;

&lt;span class="c1"&gt;// When you receive answer message from the server.&lt;/span&gt;
&lt;span class="nx"&gt;publishTransport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regarding subscribers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;subscribeTransport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;icecandidate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Send `candidate` to the server. The server has to call `add_ice_candidate` method with this parameter.&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// When you receive candidateInit message from the server.&lt;/span&gt;
&lt;span class="nx"&gt;subscribeTransport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addIceCandidate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidateInit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// When you receive offer message from the server.&lt;/span&gt;
&lt;span class="nx"&gt;subscribeTransport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Send `answer` to the server. The server has to call `set_answer` method with this parameter.&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Subscribe a track.&lt;/span&gt;
&lt;span class="nx"&gt;subscribeTransport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publisherId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MediaStream&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="nx"&gt;remoteVideo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;srcObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Examples
&lt;/h3&gt;

&lt;p&gt;I prepared an example server. Since this flow is complicated, please refer to these examples.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/h3poteto/rheomesh/blob/master/sfu/examples/media_server.rs" rel="noopener noreferrer"&gt;https://github.com/h3poteto/rheomesh/blob/master/sfu/examples/media_server.rs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The corresponding client-side JavaScript code is here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/h3poteto/rheomesh/blob/master/client/example/multiple/src/pages/room.tsx" rel="noopener noreferrer"&gt;https://github.com/h3poteto/rheomesh/blob/master/client/example/multiple/src/pages/room.tsx&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Relay Mechanism: A Unique Approach to Load Balancing
&lt;/h2&gt;

&lt;p&gt;One of the library's key innovations is its relay mechanism—a method for distributing load across SFU servers. I called this relay, but there doesn't seem to be a fixed name for it. In libraries like mediasoup, this concept is often called a pipe.&lt;/p&gt;

&lt;p&gt;Usually, SFU server, does not support clustering or load balancing. Because an SFU server is just one of peers in WebRTC stack, so the idea of splitting it into multiple instances isn't something that's natively supported. Because of this, performance is inherently limited by the server's specs, and ensuring fault tolerance by running multiple instances is also challenging.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Relay Works
&lt;/h3&gt;

&lt;p&gt;Typically, RTP packets received from client &lt;code&gt;a&lt;/code&gt; by server &lt;code&gt;A&lt;/code&gt; can only be viewed by clients connected to server &lt;code&gt;A&lt;/code&gt; (client &lt;code&gt;c&lt;/code&gt;). The relay mechanism allows these packets to be forwarded to clients connected to server &lt;code&gt;B&lt;/code&gt;, effectively enabling cross-server streaming.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxiqkftqomwwdyan3uu2l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxiqkftqomwwdyan3uu2l.png" alt="LoadBalancing issue" width="800" height="1174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Current Implementation
&lt;/h3&gt;

&lt;p&gt;For now, I’ve built a simple prototype that transfers RTP packets from server &lt;code&gt;A&lt;/code&gt; to server &lt;code&gt;B&lt;/code&gt; using UDP. However, this communication does not utilize the WebRTC stack. It’s just a direct transfer of RTP packets over UDP. Of course, I’ve implemented logic to ensure that the source and destination can be identified, but it's not using DTLS, and the receiving side is just a basic UDP server. This means that RTP packets for various destinations are all coming in through the same UDP server. The performance implications of this approach are still unknown.&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue: RTCP feedbacks
&lt;/h3&gt;

&lt;p&gt;RTCP is a protocol where the receiving side sends feedback to the sending side, and in most WebRTC implementations, it flows from the receiver to the sender. Whether or not to handle it is up to the implementation, but it will be sent regardless. However, as mentioned earlier, the relay servers in the current setup are only forwarding RTP packets, so RTCP, which involves reverse-direction communication, is not being handled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnitxwqd8yemweg1fuj16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnitxwqd8yemweg1fuj16.png" alt="RTCP issue" width="800" height="859"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For server &lt;code&gt;A&lt;/code&gt;, since client &lt;code&gt;a&lt;/code&gt; is directly connected to it, RTCP packets can simply be forwarded there. However, for server &lt;code&gt;B&lt;/code&gt;, the current architecture does not account for communication in the direction of server &lt;code&gt;B&lt;/code&gt; -&amp;gt; server &lt;code&gt;A&lt;/code&gt;, meaning there's no way to send RTCP feedback back to the original sender. If I need to implement this, it would essentially require bidirectional communication. At that point, if I’m going to establish bidirectional communication over UDP anyway, I might as well switch to WebRTC, which would simplify the process.&lt;/p&gt;

&lt;p&gt;Because of this, I might change this approach in the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenges and Future Improvements
&lt;/h3&gt;

&lt;p&gt;I suspect that making this server-to-server relay WebRTC-based would result in a more stable implementation. However, there are several concerns.&lt;/p&gt;

&lt;p&gt;The first is signaling—server-to-server communication requires its own signaling mechanism, separate from client-server signaling. This raises the question of whether signaling should be built into the library itself. I haven’t fully settled on an answer yet, but if I were to use something, gRPC seems like a viable option.&lt;/p&gt;

&lt;p&gt;Another concern is port usage. When WebRTC communicates over UDP, it consumes a port, and typically, that port isn't reused. This means that every time a server receives RTP packets from a client, it consumes a port. If another client connects to the same server, another port is consumed. Linux only supports up to 65,535 ports, and many lower-numbered ports are reserved for other purposes, making them unavailable for UDP. This means there’s a hard limit on the number of users that can be supported. Moreover, if server-to-server RTP relaying also consumes ports, the number of ports used can increase significantly. For example, if a server receives a single video stream and relays it to 10 other servers, it would use 1 port for receiving and 10 ports for relaying. On top of that, additional ports are required for clients receiving the stream. This becomes a serious issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;I haven't checked the performance in a production environment, but, currently, it works in my local machine properly, so the performance is not completely unusable.&lt;/p&gt;

&lt;p&gt;The only way to check the performance is to prepare many clients and stream simultaneously. I plan to try this at some point. However, if you use this library and have any performance issues, I would appreciate it if you could report them to me. I will improve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I use Rust
&lt;/h2&gt;

&lt;p&gt;First of all, recently, I like to write Rust. It has powerful memory management capabilities that extend to parallel processing. In the SFU, a lot of parallel processing is required, but at the same time, high-speed single-thread performance such as receiving and sending RTP packet is also required. I thought Rust would be a good fit for this requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Developing an SFU library from scratch has been an interesting challenge. By leveraging Rust’s strong memory safety and concurrency features, I’ve been able to build a system that efficiently processes RTP packets while keeping SFU-related logic separate from signaling.&lt;/p&gt;

&lt;p&gt;While the current implementation works well locally, significant improvements are needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enhanced relay mechanism&lt;/li&gt;
&lt;li&gt;Comprehensive performance testing&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/h3poteto/rheomesh/issues/49" rel="noopener noreferrer"&gt;E2E tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/h3poteto/rheomesh/issues/150" rel="noopener noreferrer"&gt;Recording feature&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I welcome feedback and contributions from the community. If you're interested in WebRTC SFU development in Rust, I'd love to hear your thoughts!&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>sfu</category>
      <category>rust</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
