Wrestling with WebRTC
The Wrestling Backstory
Not too long ago, I helped my club wrestling team host a competition across three schools. To score a college wrestling match, you need a scoreboard, a timer to track how much time is left in a period, and a special clock to track the riding time for the match. Riding time is simple to calculate: while a wrestler has control of their opponent, they gain riding time. The riding time of a match is shown as the absolute value of the difference of both wrestlers total time in control with an indicator of who has more. Below is an example of a wrestling scoreboard:
The gym we used to host the competition already scoreboard that could handle the first three criteria, but we needed a way to track riding time. The timer we needed was simple, in fact it was simple enough to create a Claude Artifact last minute to score the event. However, I decided to create a better version of this site which led me down the rabbit hole that is WebRTC.
Building a Better Timer
While the Claude Artifact was good enough for the event, I decided to build a better one. There were 2 pain points I had while using the old version:
- No way to manually adjust the riding time.
- No way to simultaneously control the timer and show it to the ref.
Pain point 1 was trivial to implement (reset the timer with an offset for each adjustment). Pain point 2 turned out to be a lot more interesting. During the match, we only had a single computer set aside to control the clock, so only the operator of the clock could see the riding time of the match, making it hard for the ref to verify it was running correctly.
There were two approaches that came to mind to solve the problem:
- Mirror the computer's screen to a separate display (ex. tv, external monitor)
- Extend website with a page that displays the riding time from another browser's clock
Approach 1 is inconvenient as it requires platform specific technology (ex. airplay) or extra hardware (ex. tv, hdmi cord, and a power supply). Approach 2 is more flexible as it only requires two devices with modern browsers (ex. smartphones). Approach 2 is also a more interesting problem to work on so I went to work on designing a way to sync 2 instances of my site.
Approaches To Sync
While brainstorming, I came up with three approaches to how to sync the instances:
- Use a managed synchronization service like Convex or Zero
- Use Cloudflare Durable Objects to store clock sync and have both sites connect via WebSockets
- Connect the two websites directly via a peer to peer protocol (WebRTC)
One of my goals was to make this run for as cheap as possible which crossed out approach one. Approach two was/is viable but I needed a Cloudflare paid plan to use durable objects which led me to start with implementing approach three, which didn't need (as many) costly cloud servers.
The WebRTC protocol
Before we go into my implementation, it's important to understand what WebRTC is and how it works. At it core, WebRTC is a protocol built into most browsers that allows peer to peer connections. I'll be mainly using it to send data across devices but it can also be used to implement phone calls, video streaming and more.
Despite being peer to peer, a server is still needed to setup the connection. STUN servers are used to attempt setup the connection. If setting up a direct connection is impossible (likely due to NAT issues), a TURN server is used as a bridge to connect the two peers.
Implementation
My final design for Wrestle-Time consists of 4 elements:
- A "controller" to control the riding time tracker
- A "receiver" to display the current riding time
- A key-value database with WebRTC session metadata
- Cloudflare Calls (WebRTC SFU platform)
- A Selective Forwarding Unit (SFU) routes WebRTC connections between multiple participants instead of requiring each peer to connect to each other directly (helpful at scale)
Here's how it works:
- Browser desired to control riding timer enters site root which generates a controller with a unique code and WebRTC Session.
- Controller writes the WebRTC session metadata to database with generated code as key.
- Browser desired to display riding rime enters
/receive/${code}
path on site. - Receiver fetches session data from database and creates a connection to Controller via Cloudflare Calls
- Whenever riding time controller updates, updates are sent to the receiver in real time.
Currently all of this is run on Cloudflare infrastructure under the free plan. This means that I am limited to creating 1,000 sessions per day unless I upgrade to the $5/month paid plan where it is then 1,000,000 sessions.
Additionally using Cloudflare TURN servers and SFU's cost $0.05/gigabyte for outbound traffic. TURN servers are free when using Cloudflare SFU's and the first 1,000 gigabytes/month used towards either are free. Cloudflare STUN servers are free to use and public. This led me to use the SFU service as it cost the same while also scaling better for most cases.
Final Results
With WebRTC, I was able to create a scalable, simple to use riding time tracker for wrestling officials. All an official needs to do is enter the Wrestle-Time.com site and copy the displayed code onto another device. Both devices will then be synced to display the same data with the original device being able to make manual changes.
Caveats
WebRTC has some downsides I wanted to address. The first being that the implementation of the protocol can vary from browser to browser, making it less reliable than other protocols like websockets. There are adapters that help mitigate this problem, but I occasionally have difficulty connecting devices together.
Another downside is lack of mature tooling around the WebRTC protocol. The most popular libraries I saw were either unmaintained (SimpleRTC) paid services (Twilio), or required separate servers(PeerJS). Luckily, learning the WebRTC Browser API didn't turn out to be too bad.
Reflection
Overall, I'm pretty happy with the cost and performance of my little riding time app. I'll likely continue to add functionality until it can replace a wrestling scoreboard. As I continue to work on it, I think it's important to point out that WebRTC may not be the best option if I ever decide to monetize the project. Using a a central server or solution like Cloudflare Durable Objects to track state would likely be more reliable at a not much higher cost (especially at my smaller scale). While I enjoy playing with shiny new tech like peer to peer WebRTC, projects like these remind that "old-fashioned" approaches are often simpler, better documented, and can offer similar if not better performance when optimized.