This project demonstrates real-time audio and video capture using the Zoom RTMS. It streams the Audio and Video to AWS IVS via ffmpeg in real-time. This advance project includes injection of empty buffer. Black video keyframes are injected when user's video is turned off.
Before running the application, ensure you have the following environment variables set in a .env file:
ZOOM_SECRET_TOKEN: Secret token for URL validationZOOM_CLIENT_ID: Zoom client IDZOOM_CLIENT_SECRET: Zoom client secret
PORT: The port on which the Express server runs (default: 3000)IVS_RTMP_URL: RTMP URL for your AWS IVS channel (e.g., rtmps://your-endpoint/app/your-stream-key)
The application follows this sequence:
- Starts an Express server on port 3000
- Listens for webhook events at
/webhookendpoint - Handles URL validation challenges from Zoom
- When a meeting starts:
- Receives
meeting.rtms_startedevent - Establishes WebSocket connection to signaling server
- Sends handshake with authentication signature
- Receives media server URL from signaling server
- Establishes WebSocket connection to media server
- Starts IVS streaming with FFmpeg pipes for audio and video
- Sends media handshake with authentication
- Begins receiving audio and video data
- Receives
- During the meeting:
- Maintains WebSocket connections with keep-alive messages
- Audio data is written directly to FFmpeg pipe in real-time
- Video data is buffered with timestamps and processed every 40ms
- Timer processes buffered video packets, using the most recent packet or injecting black frames for gaps
- FFmpeg reads from pipes and streams to IVS
- Logs timing information including timer drift, packet intervals, and buffer status
- Handles any connection errors
- When a meeting ends:
- Receives
meeting.rtms_stoppedevent - Stops FFmpeg streaming to IVS
- Closes all active WebSocket connections
- Receives
-
Start the server:
node index.js
-
Start a Zoom meeting. The application will:
- Receive the
meeting.rtms_startedevent - Establish WebSocket connections
- Setup audio and video pipe to prepare for IVS streaming via ffmpeg
- Begin receiving audio and video data, send them as buffer into respective audio and video pipes
- After meeting ends, stops ffmpeg streaming to IVS
- Receive the
- Real-time audio data capture and immediate streaming (16kHz, mono)
- Real-time video data capture with buffering and gap filling (H264, 720p, 25fps)
- WebSocket connection management for both signaling and media servers
- Timer-based video processing every 40ms with black frame injection for gaps
- Comprehensive logging including timer drift, packet intervals, and buffer status
- FFmpeg streams to IVS by reading from audio and video pipes
- Keep-alive message handling for connection stability
- Error handling for WebSocket connections
- URL validation handling
- Audio data is written directly to FFmpeg pipe in real-time without buffering
- Video data is buffered with timestamps and processed in batches every 40ms
- Black video frames are injected automatically when no video packets are available
- Timer drift is monitored and logged for performance tracking
- Packet intervals are logged to monitor stream timing patterns
- Server runs on port 3000 by default, if PORT is not specified in .env
- Webhook endpoint is available at
http://localhost:3000/webhook - Requires FFmpeg to be installed and accessible in your PATH
- Node.js (v14 or higher recommended)
- FFmpeg installation:
- macOS:
brew install ffmpeg - Ubuntu/Debian:
sudo apt-get install ffmpeg - Windows: Download from FFmpeg website
- macOS:
- ngrok for exposing your local server to the internet
- Zoom App configuration with RTMS scopes enabled
-
IVS Stream does not work:
- Verify FFmpeg is installed and accessible in your PATH
- Verify that you are subscribing to H264 video format, and uncompressed raw audio format
- Check that the Zoom app has the correct RTMS scopes
- Ensure the webhook URL is correctly configured in the Zoom app
- Verify the IVS_RTMP_URL is correct and the IVS channel is active
-
Connection Issues:
- Verify ngrok is running and the tunnel is active
- Check that the Zoom app credentials in
.envare correct - Ensure the webhook endpoint is accessible from the internet
-
Known Issues:
- There might be video artifacts generated on IVS. Additional fine tuning is necessary on ffmpeg parameters.
The project jitter-buffers RTMS media before streaming it to Amazon IVS. Its multi-stage Dockerfile keeps build tooling out of the final runtime image and does not hard-code a CPU architecture.
Build and run it from the rtms-samples repository root:
docker build -f streaming/stream_to_aws_ivs_jitter_buffer_js/Dockerfile -t rtms-streaming-stream_to_aws_ivs_jitter_buffer_js .
docker run --rm --env-file streaming/stream_to_aws_ivs_jitter_buffer_js/.env -p 3000:3000 rtms-streaming-stream_to_aws_ivs_jitter_buffer_jsRun the build from the repository root because the Dockerfile uses repository-relative paths. Runtime secrets are supplied with --env-file and are excluded from the image build context.
Normal Zoom webhook deliveries are verified against the exact raw request body using
x-zm-signature and x-zm-request-timestamp. Configure ZOOM_SECRET_TOKEN with the
Marketplace app's webhook Secret Token. Requests with missing, invalid, or stale
signatures are rejected; the default replay window is 300 seconds and can be changed
with WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS.