Overview
Hyperscape streams gameplay to multiple RTMP destinations simultaneously using FFmpeg’s tee muxer for efficient single-encode multi-output.
- Twitch -
rtmp://live.twitch.tv/app
- Kick -
rtmps://fa723fc1b171.global-contribute.live-video.net/app (RTMPS)
- X/Twitter -
rtmp://sg.pscp.tv:80/x
- YouTube - Explicitly disabled (set
YOUTUBE_STREAM_KEY="" to prevent stale keys)
YouTube was removed from default streaming destinations in February 2026. To re-enable, set YOUTUBE_STREAM_KEY and YOUTUBE_RTMP_URL.
Configuration
Environment Variables
# Twitch
TWITCH_STREAM_KEY=live_123456789_abcdefghij
TWITCH_RTMP_URL=rtmp://live.twitch.tv/app # Optional override
# Kick (uses RTMPS - secure RTMP)
KICK_STREAM_KEY=sk_us-west-2_OrgZh8XyN0Qs_...
KICK_RTMP_URL=rtmps://fa723fc1b171.global-contribute.live-video.net/app
# X/Twitter
X_STREAM_KEY=sp16tpmtyqws
X_RTMP_URL=rtmp://sg.pscp.tv:80/x
# YouTube (optional - disabled by default)
YOUTUBE_STREAM_KEY=
YOUTUBE_RTMP_URL=rtmp://a.rtmp.youtube.com/live2
Get Stream Keys
Twitch:
- Go to Twitch Dashboard
- Copy Primary Stream Key
- Set as
TWITCH_STREAM_KEY
Kick:
- Go to Kick Creator Dashboard
- Navigate to Stream Settings
- Copy Stream Key
- Set as
KICK_STREAM_KEY
X/Twitter:
- Go to Media Studio
- Click Producer → Create Broadcast → Create Source
- Copy RTMP URL and Stream Key
- Set as
X_RTMP_URL and X_STREAM_KEY
X/Twitter streaming requires X Premium subscription for desktop streaming.
Streaming Quality
Balanced Mode (Default)
Optimized for smooth playback and viewer experience:
Settings:
- Tune:
film (allows B-frames for better compression)
- Buffer: 4x bitrate (18000k for 4500k bitrate)
- Lookahead: Better bitrate smoothing
- Use Case: Passive viewing, longer streams, unstable connections
FFmpeg Args:
-c:v libx264 -preset ultrafast -tune film \
-b:v 4500k -bufsize 18000k -bf 2 \
-g 60 -keyint_min 60
Low Latency Mode
Optimized for minimal delay:
Settings:
- Tune:
zerolatency (no B-frames)
- Buffer: 2x bitrate (9000k for 4500k bitrate)
- No lookahead: Immediate encoding
- Use Case: Interactive streams, live betting, viewer interaction
FFmpeg Args:
-c:v libx264 -preset ultrafast -tune zerolatency \
-b:v 4500k -bufsize 9000k \
-g 60 -keyint_min 60
Quality Settings
# Video bitrate (default: 4500k)
STREAM_VIDEO_BITRATE=4500
# Audio bitrate (default: 128k)
STREAM_AUDIO_BITRATE=128
# Framerate (default: 30)
STREAM_FPS=30
# Resolution (default: 1280x720)
STREAM_WIDTH=1280
STREAM_HEIGHT=720
The canonical platform determines default public delay for anti-cheat timing:
# Default: twitch (12s latency)
STREAMING_CANONICAL_PLATFORM=twitch
# Override public delay
STREAMING_PUBLIC_DELAY_MS=0 # Live betting mode (no delay)
Platform Defaults:
youtube → 15000ms delay
twitch → 12000ms delay
hls → 4000ms delay
Set STREAMING_PUBLIC_DELAY_MS=0 for live betting with no delay. This disables anti-cheat timing protection.
Audio Configuration
Enable Audio Capture
# Enable audio (default: true)
STREAM_AUDIO_ENABLED=true
# PulseAudio device
PULSE_AUDIO_DEVICE=chrome_audio.monitor
# PulseAudio server
PULSE_SERVER=unix:/tmp/pulse-runtime/pulse/native
# Runtime directory
XDG_RUNTIME_DIR=/tmp/pulse-runtime
See Audio Streaming for complete PulseAudio setup.
Disable Audio
STREAM_AUDIO_ENABLED=false
FFmpeg will use silent audio (anullsrc).
FFmpeg Configuration
Complete Command
ffmpeg \
# Audio input (PulseAudio)
-f pulse -i chrome_audio.monitor \
-thread_queue_size 1024 \
-use_wallclock_as_timestamps 1 \
-filter:a aresample=async=1000:first_pts=0 \
\
# Video input (X11 screen capture)
-f x11grab -i :99 \
-thread_queue_size 1024 \
-framerate 30 \
\
# Video encoding
-c:v libx264 -preset ultrafast -tune film \
-b:v 4500k -bufsize 18000k -bf 2 \
-g 60 -keyint_min 60 \
-pix_fmt yuv420p \
\
# Audio encoding
-c:a aac -b:a 128k -ar 44100 \
\
# FLV flags for RTMP stability
-flvflags no_duration_filesize \
\
# Output to multiple destinations (tee muxer)
-f tee \
"[f=flv]rtmp://live.twitch.tv/app/your-twitch-key|\
[f=flv]rtmps://fa723fc1b171.global-contribute.live-video.net/app/your-kick-key|\
[f=flv]rtmp://sg.pscp.tv:80/x/your-x-key"
Audio Buffering:
-thread_queue_size 1024 # Prevent buffer underruns
-use_wallclock_as_timestamps 1 # Real-time timing
-filter:a aresample=async=1000:first_pts=0 # Recover from drift
Video Buffering:
-thread_queue_size 1024 # Increased from 512
-genpts # Generate presentation timestamps
-discardcorrupt # Discard corrupt frames
Output Buffering
# Balanced mode (default)
-bufsize 18000k # 4x bitrate (4500k * 4)
# Low latency mode
-bufsize 9000k # 2x bitrate (4500k * 2)
Kick RTMP URL Fix
Problem
Kick streaming used incorrect URL:
# ❌ Old (incorrect)
KICK_RTMP_URL=rtmp://ingest.kick.com/live
Solution
Use proper IVS endpoint with RTMPS:
# ✅ New (correct)
KICK_RTMP_URL=rtmps://fa723fc1b171.global-contribute.live-video.net/app
Why:
- Kick uses AWS IVS (Interactive Video Service)
- Regional ingest endpoints (not generic domain)
- RTMPS (RTMP over TLS) for secure streaming
/app path required
Stream Key Security
Masked Logging
Stream keys are masked in logs:
echo "[deploy] TWITCH_STREAM_KEY: ${TWITCH_STREAM_KEY:+***configured***}"
echo "[deploy] KICK_STREAM_KEY: ${KICK_STREAM_KEY:+***configured***}"
echo "[deploy] X_STREAM_KEY: ${X_STREAM_KEY:+***configured***}"
Output:
[deploy] TWITCH_STREAM_KEY: ***configured***
[deploy] KICK_STREAM_KEY: ***configured***
[deploy] X_STREAM_KEY: ***configured***
Explicit Unset
Prevent stale stream keys from being used:
# Unset all stream keys
unset TWITCH_STREAM_KEY X_STREAM_KEY X_RTMP_URL KICK_STREAM_KEY KICK_RTMP_URL
unset YOUTUBE_STREAM_KEY YOUTUBE_RTMP_STREAM_KEY
# Explicitly disable YouTube
export YOUTUBE_STREAM_KEY=""
# Re-source .env for correct values
source /root/hyperscape/packages/server/.env
Streaming Diagnostics
RTMP Status File
cat packages/server/public/live/rtmp-status.json
Example Output:
{
"active": true,
"destinations": [
{
"name": "Twitch",
"url": "rtmp://live.twitch.tv/app",
"status": "streaming",
"uptime": 12345
},
{
"name": "Kick",
"url": "rtmps://fa723fc1b171.global-contribute.live-video.net/app",
"status": "streaming",
"uptime": 12345
},
{
"name": "X",
"url": "rtmp://sg.pscp.tv:80/x",
"status": "streaming",
"uptime": 12345
}
]
}
Check FFmpeg Processes
# List FFmpeg processes
ps aux | grep ffmpeg
# Check FFmpeg logs
tail -f /var/log/ffmpeg.log
Check PM2 Logs
# Filter for streaming keywords
bunx pm2 logs hyperscape-duel --lines 200 | grep -iE "rtmp|ffmpeg|stream|capture|destination|twitch|kick|frame|fps|bitrate"
Troubleshooting
Stream Not Appearing
1. Check stream keys are set:
echo $TWITCH_STREAM_KEY
echo $KICK_STREAM_KEY
echo $X_STREAM_KEY
2. Check FFmpeg is running:
3. Check RTMP status:
cat packages/server/public/live/rtmp-status.json
4. Check platform dashboards:
Audio Not in Stream
See Audio Streaming Troubleshooting.
Buffering Issues
Increase buffer size:
# 6x buffer (27000k for 4500k bitrate)
STREAM_VIDEO_BITRATE=4500
# Then set bufsize to 6x in FFmpeg args
Use balanced mode:
Connection Drops
Check network stability:
ping -c 10 live.twitch.tv
ping -c 10 sg.pscp.tv
Increase reconnect attempts:
# In FFmpeg args
-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5
High CPU Usage
Reduce encoding preset:
# Change from ultrafast to faster
-preset faster
Reduce resolution:
STREAM_WIDTH=1280
STREAM_HEIGHT=720
Reduce framerate:
Advanced Configuration
Custom RTMP Destination
CUSTOM_RTMP_NAME=Custom
CUSTOM_RTMP_URL=rtmp://your-server/live
CUSTOM_STREAM_KEY=your-key
JSON Fanout Config
For additional destinations:
RTMP_DESTINATIONS_JSON='[
{
"name": "MyMux",
"url": "rtmp://host/live",
"key": "stream-key",
"enabled": true
}
]'
RTMP Multiplexer
Use a multiplexer service (Restream, Livepeer) for fanout:
RTMP_MULTIPLEXER_NAME=Restream
RTMP_MULTIPLEXER_URL=rtmp://live.restream.io/live
RTMP_MULTIPLEXER_STREAM_KEY=your-restream-key
Benefits:
- Single stream to multiplexer
- Multiplexer fans out to all platforms
- Reduces server bandwidth and CPU usage
Local Testing
nginx-rtmp Server
Test RTMP streaming locally:
# Start nginx-rtmp container
docker run -d -p 1935:1935 tiangolo/nginx-rtmp
# Configure Hyperscape to stream to localhost
CUSTOM_RTMP_URL=rtmp://localhost:1935/live
CUSTOM_STREAM_KEY=test
# View stream
ffplay rtmp://localhost:1935/live/test
Test Command
This starts streaming to local nginx-rtmp server for testing.
Production Deployment
PM2 Configuration
// From ecosystem.config.cjs
env: {
// Streaming destinations
TWITCH_STREAM_KEY: process.env.TWITCH_STREAM_KEY || "",
KICK_STREAM_KEY: process.env.KICK_STREAM_KEY || "",
KICK_RTMP_URL: process.env.KICK_RTMP_URL || "rtmps://fa723fc1b171.global-contribute.live-video.net/app",
X_STREAM_KEY: process.env.X_STREAM_KEY || "",
X_RTMP_URL: process.env.X_RTMP_URL || "rtmp://sg.pscp.tv:80/x",
YOUTUBE_STREAM_KEY: "", // Explicitly disabled
// Timing
STREAMING_CANONICAL_PLATFORM: "twitch",
STREAMING_PUBLIC_DELAY_MS: "0",
// Quality
STREAM_LOW_LATENCY: "false",
}
GitHub Secrets
Configure in Settings → Secrets → Actions:
| Secret | Purpose |
|---|
TWITCH_STREAM_KEY | Twitch stream key |
KICK_STREAM_KEY | Kick stream key |
KICK_RTMP_URL | Kick RTMP URL |
X_STREAM_KEY | X/Twitter stream key |
X_RTMP_URL | X/Twitter RTMP URL |
Deployment Script
The scripts/deploy-vast.sh explicitly manages stream keys:
# Unset stale keys
unset TWITCH_STREAM_KEY X_STREAM_KEY X_RTMP_URL KICK_STREAM_KEY KICK_RTMP_URL
unset YOUTUBE_STREAM_KEY YOUTUBE_RTMP_STREAM_KEY
# Disable YouTube
export YOUTUBE_STREAM_KEY=""
# Re-source .env
source /root/hyperscape/packages/server/.env
# Start PM2
bunx pm2 start ecosystem.config.cjs
Monitoring
Stream Health
# Check streaming API
curl -s "http://localhost:5555/api/streaming/state" | jq .
# Expected output:
{
"active": true,
"destinations": ["Twitch", "Kick", "X"],
"uptime": 12345,
"fps": 30,
"bitrate": 4500
}
FFmpeg Stats
# Check FFmpeg output
tail -f /var/log/ffmpeg.log | grep -E "fps|bitrate|frame"
# Expected output:
frame= 1234 fps=30 q=28.0 size= 12345kB time=00:00:41.23 bitrate=4500.0kbits/s
Twitch:
Kick:
- Creator Dashboard
- Shows stream status and viewers
X: