6.4 KiB
6.4 KiB
Live Streaming Setup Guide
This guide will help you set up hybrid live streaming with HLS + automatic VOD archiving to R2.
Architecture
OBS → RTMP → nginx-rtmp → HLS chunks → Real-time uploader → R2 → Cloudflare Worker → Viewers
↓
VOD Archive
Quick Start
1. Start the Streaming Stack
cd streaming
./start-streaming.sh
This will:
- Start nginx-rtmp server (Docker)
- Start HLS chunk uploader to R2
- Display connection info
2. Configure OBS
Settings → Stream
- Service: Custom
- Server:
rtmp://localhost/live - Stream Key:
my-stream(or choose your own name likegaming,podcast, etc.)
Settings → Output
Recommended Settings for 1080p60:
- Output Mode: Advanced
- Encoder: x264 or Hardware (NVENC/AMD/QuickSync)
- Rate Control: CBR
- Bitrate: 6000 Kbps (adjust based on upload speed)
- Keyframe Interval: 2 seconds
- CPU Usage Preset: veryfast (or faster for lower-end CPUs)
- Profile: high
- Tune: zerolatency
For 720p60:
- Bitrate: 4500 Kbps
For 1080p30:
- Bitrate: 4500 Kbps
Settings → Video
- Base Resolution: 1920x1080 (or your monitor resolution)
- Output Resolution: 1920x1080 or 1280x720
- FPS: 60 or 30
Settings → Advanced
- Process Priority: High (optional, for smoother streaming)
3. Start Streaming
- Click "Start Streaming" in OBS
- Wait 5-10 seconds for HLS chunks to be created
- Access your stream at:
- Local (testing):
http://localhost:8081/hls/my-stream.m3u8 - Public:
https://videos.jeffemmett.com/live/my-stream/my-stream.m3u8
- Local (testing):
4. Watch Your Stream
In a browser:
- Use the HLS.js player
- Or use VLC: Media → Open Network Stream → Enter URL
Test Player HTML:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
</head>
<body>
<video id="video" controls style="width: 100%; max-width: 1280px;"></video>
<script>
const video = document.getElementById('video');
const src = 'https://videos.jeffemmett.com/live/my-stream/my-stream.m3u8';
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(src);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
video.play();
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = src;
}
</script>
</body>
</html>
5. Stop Streaming
- Click "Stop Streaming" in OBS
- (Optional) Stop the streaming stack:
./stop-streaming.sh
How It Works
Real-Time Streaming
- OBS sends RTMP stream to
rtmp://localhost/live/my-stream - nginx-rtmp receives the stream and segments it into HLS chunks:
- Creates
.m3u8playlist (updated every 2 seconds) - Creates
.tsvideo chunks (2-second segments)
- Creates
- HLS Uploader watches the
hls/directory:- Detects new chunks as they're created
- Immediately uploads to R2 at
live/my-stream/
- Cloudflare Worker serves the chunks:
- Viewers request
.m3u8playlist - Player loads
.tschunks as they become available - ~5-10 second latency end-to-end
- Viewers request
Automatic VOD Archiving
- All HLS chunks remain in R2 after stream ends
- Can be concatenated into full VOD recording
- Or left as is for HLS replay
Troubleshooting
Stream won't connect
- Check if nginx-rtmp is running:
docker ps | grep obs-streaming-server - Check nginx logs:
docker logs obs-streaming-server - Verify RTMP URL:
rtmp://localhost/live(notrtmp://localhost:1935/live)
Stream is choppy/buffering
- Lower bitrate in OBS Output settings
- Check CPU usage - try faster encoder preset
- Check upload bandwidth - run speed test
Can't see stream on public URL
- Wait 5-10 seconds after starting stream
- Check HLS uploader logs:
tail -f streaming/hls-uploader.log - Verify chunks are being uploaded to R2
High latency (>15 seconds)
- Normal for HLS - typical latency is 6-15 seconds
- Lower
hls_fragmentin nginx.conf to 1s (will increase bandwidth) - Consider WebRTC for sub-second latency (different setup)
Advanced Configuration
Multiple Quality Levels
Edit streaming/nginx/nginx.conf:
hls_variant _360p BANDWIDTH=800000;
hls_variant _720p BANDWIDTH=2800000;
hls_variant _1080p BANDWIDTH=5000000;
Change Stream Key
Use any stream key you want - it becomes the folder name in R2:
- Stream key:
my-stream→ R2 path:live/my-stream/(default) - Stream key:
gaming→ R2 path:live/gaming/ - Stream key:
podcast-ep-5→ R2 path:live/podcast-ep-5/
Custom Domain
If using a different domain, update in .env:
PUBLIC_DOMAIN=streams.yourdomain.com
Performance Notes
Bandwidth Usage
- 720p60 @ 4500kbps: ~2.0 GB/hour
- 1080p60 @ 6000kbps: ~2.7 GB/hour
- 1080p30 @ 4500kbps: ~2.0 GB/hour
R2 Costs
- Storage: $0.015/GB/month
- Class A Operations (uploads): $4.50/million
- Class B Operations (downloads): $0.36/million
- Egress: Free to Cloudflare network
Example cost for 1 hour stream at 1080p60:
- Storage: ~2.7GB × $0.015 = $0.04/month
- Uploads: ~1800 chunks × $0.0000045 = $0.008
- Total: ~$0.05 per hour streamed
Viewer Bandwidth
Viewers consume:
- 720p: ~4.5 Mbps
- 1080p: ~6 Mbps
File Structure
streaming/
├── docker-compose.yml # nginx-rtmp container config
├── nginx/
│ └── nginx.conf # nginx RTMP + HLS config
├── scripts/
│ ├── on_publish.sh # Called when stream starts
│ └── on_publish_done.sh # Called when stream ends
├── hls/ # HLS output directory (auto-created)
│ ├── my-stream.m3u8 # Playlist
│ └── my-stream-*.ts # Video chunks
├── start-streaming.sh # Start everything
├── stop-streaming.sh # Stop everything
└── STREAMING-SETUP.md # This file
Next Steps
- Create a custom player page
- Set up stream notifications (when stream goes live)
- Add stream recording/VOD conversion
- Implement chat/comments
- Add stream analytics
Support
For issues, check:
- Docker logs:
docker logs obs-streaming-server - HLS uploader logs:
tail -f streaming/hls-uploader.log - Nginx stats:
http://localhost:8081/stat