rtmp-rs
Async RTMP server and client library for Rust - Build live video streaming infrastructure with Tokio.
rtmp-rs is an RTMP server and client library written in Rust, built to handle the messy reality of live streaming. Encoders like OBS and FFmpeg all have their quirks, so rtmp-rs uses lenient parsing to roll with it instead of rejecting non-conforming streams. Late joiners get instant playback with keyframe caching, and smart backpressure handling keeps audio flowing for slow subscribers while selectively dropping video frames. If you're building a streaming service or relay and want RTMP ingest that just works, this might be what you're looking for.
Features
- Async/Await - Built on Tokio for high-performance concurrent connections
- Zero-Copy - Uses
bytes::Bytesthroughout for efficient memory handling - Backpressure Handling - Slow subscribers drop video frames while audio keeps flowing, so viewers hear continuous sound instead of staring at a frozen frame
- Built-in Pub/Sub - Stream key routing works out of the box; no code required
- Late-Joiner GOP Cache - Buffers keyframes so viewers don't wait for the next IDR frame when joining mid-stream
- Lenient Parsing - Handles encoder quirks like empty app names and timestamp regression (OBS, Twitch compatible)
- Extensible - Optional
RtmpHandlercallbacks for custom auth and media processing
Quick Start
1. Add dependency:
2. Create a server:
use ;
use SessionContext;
use ;
;
async
3. Stream to it:
# OBS: Server: rtmp://localhost/live Stream Key: test
# ffmpeg:
Client Example (Pull Stream)
use ;
async
Handler Callbacks
The RtmpHandler trait provides optional hooks for custom logic. All callbacks have sensible defaults that accept connections and allow streams - you only override what you need:
use ;
use SessionContext;
use PublishParams;
;
Available callbacks:
| Callback | Use Case |
|---|---|
on_connection |
IP blocklist, rate limiting |
on_handshake_complete |
Post-handshake setup, before connect command, logging |
on_connect |
Validate app name, parse auth tokens from tcUrl |
on_disconnect |
Connection cleanup, logging |
on_fc_publish |
Early stream key validation (OBS sends this first) |
on_publish |
Main stream key authentication |
on_publish_stop |
Publisher cleanup, notifications |
on_play |
Subscriber authorization |
on_pause |
Handle subscriber pause |
on_unpause |
Handle subscriber resume |
on_metadata |
Capture stream info (resolution, bitrate, codec) |
on_media_tag |
Raw FLV tag access, custom filtering |
on_video_frame |
Process H.264 NALUs |
on_audio_frame |
Process AAC frames |
on_keyframe |
Track GOP boundaries |
Configuration
use Duration;
use ServerConfig;
let config = default
.bind
.max_connections
.chunk_size
.connection_timeout
.idle_timeout;
Testing
# Stream (publish) with ffmpeg
# Play with ffplay
AI disclaimer
This repo is a Rust rewrite of my RTMP Go server, which was my first time dabbling in video codecs and streaming. Almost all of the code in this Rust version was written by AI (Claude Opus 4.5).
I recently had an idea that required an RTMP server, so I used it as an excuse to write some Rust and try out some agentic programming. This repo is partly an experiment to see how far I could get by vibecoding the entire thing with Claude Code. The answer? Far!
The whole thing took around 8 hours. It probably could have been faster if I auto-accepted edits without reading the code, but I like to review everything the agent generates. I started with Plan Mode to define the requirements, then moved on to implementation.
That said, there was a tricky timestamp bug that caused audio/video stuttering, and Claude kept hallucinating answers instead of helping. After a deep-dive on my own, I found the root cause. I also noticed some parts of the code that could be improved, but I decided to keep things as-is for now. Any future improvements I'll have Claude handle.
License
Licensed under MIT license