npm install ws expressimport { RTMSManager } from './rtmsManager/index.js';
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.use(express.json());
// 1. Initialize
await RTMSManager.init({
credentials: {
meeting: {
clientId: process.env.ZOOM_CLIENT_ID,
clientSecret: process.env.ZOOM_CLIENT_SECRET,
secretToken: process.env.ZOOM_SECRET_TOKEN,
}
},
mediaTypes: RTMSManager.MEDIA.AUDIO | RTMSManager.MEDIA.TRANSCRIPT,
logging: 'info'
});
// 2. Handle media events
RTMSManager.on('audio', ({ buffer, userName, timestamp }) => {
console.log(`Audio from ${userName}: ${buffer.length} bytes`);
});
RTMSManager.on('transcript', ({ text, userName }) => {
console.log(`${userName}: ${text}`);
});
RTMSManager.on('error', (error) => {
console.error(error.toString()); // Pretty-printed with causes & fixes
});
// 3. Webhook endpoint
app.post('/webhook', (req, res) => {
const { event, payload } = req.body;
// Handle URL validation challenge
if (event === 'endpoint.url_validation') {
const hashForValidate = crypto
.createHmac('sha256', process.env.ZOOM_SECRET_TOKEN)
.update(payload.plainToken)
.digest('hex');
return res.json({ plainToken: payload.plainToken, encryptedToken: hashForValidate });
}
// Feed RTMS events to manager
RTMSManager.handleEvent(event, payload);
res.status(200).send();
});
app.listen(3000);RTMSManager.MEDIA.AUDIO // 1
RTMSManager.MEDIA.VIDEO // 2
RTMSManager.MEDIA.SHARESCREEN // 4
RTMSManager.MEDIA.TRANSCRIPT // 8
RTMSManager.MEDIA.CHAT // 16
RTMSManager.MEDIA.ALL // 32
// Combine with bitwise OR
const mediaTypes = RTMSManager.MEDIA.AUDIO | RTMSManager.MEDIA.TRANSCRIPT; // 9// Audio only (speech processing)
await RTMSManager.init({ ...RTMSManager.PRESETS.AUDIO_ONLY, credentials });
// Audio + transcript (captions)
await RTMSManager.init({ ...RTMSManager.PRESETS.TRANSCRIPTION, credentials });
// Audio + video (recording)
await RTMSManager.init({ ...RTMSManager.PRESETS.VIDEO_RECORDING, credentials });
// All media types
await RTMSManager.init({ ...RTMSManager.PRESETS.FULL_MEDIA, credentials });// Media events - data object contains: buffer/text, userId, userName, timestamp, meetingId, streamId
RTMSManager.on('audio', ({ buffer, userId, userName, timestamp, meetingId, streamId }) => {});
RTMSManager.on('video', ({ buffer, userId, userName, timestamp, meetingId, streamId }) => {});
RTMSManager.on('sharescreen', ({ buffer, userId, userName, timestamp, meetingId, streamId }) => {});
RTMSManager.on('transcript', ({ text, userId, userName, timestamp, meetingId, streamId }) => {});
RTMSManager.on('chat', ({ text, userId, userName, timestamp, meetingId, streamId }) => {});
// Lifecycle events
RTMSManager.on('meeting.rtms_started', (payload) => {});
RTMSManager.on('meeting.rtms_stopped', (payload) => {});
RTMSManager.on('participant_video_on', ({ participants, availableParticipants, streamId }) => {});
RTMSManager.on('participant_video_off', ({ participants, availableParticipants, streamId }) => {});
RTMSManager.on('video_subscription_response', ({ userId, success, streamId }) => {});
RTMSManager.on('stream_close_response', ({ success, streamId }) => {});
RTMSManager.on('error', (rtmsError) => {});await RTMSManager.init({
credentials,
mediaTypes: RTMSManager.MEDIA.VIDEO,
mediaParams: {
video: {
contentType: RTMSManager.MEDIA_PARAMS.MEDIA_CONTENT_TYPE_RAW_VIDEO,
codec: RTMSManager.MEDIA_PARAMS.MEDIA_PAYLOAD_TYPE_H264,
dataOpt: RTMSManager.MEDIA_PARAMS.MEDIA_DATA_OPTION_VIDEO_SINGLE_INDIVIDUAL_STREAM,
resolution: RTMSManager.MEDIA_PARAMS.MEDIA_RESOLUTION_HD,
fps: 25
}
}
});
RTMSManager.on('participant_video_on', ({ availableParticipants, streamId }) => {
console.log(streamId, availableParticipants);
});
const participants = RTMSManager.getVideoOnParticipants(streamId);
RTMSManager.subscribeToIndividualVideo(streamId, participants[0].userId);await RTMSManager.init({
credentials: {
meeting: { clientId: '...', clientSecret: '...', secretToken: '...' },
videoSdk: { clientId: '...', clientSecret: '...', secretToken: '...' }, // Optional
},
mediaTypes: RTMSManager.MEDIA.ALL,
logging: 'info', // 'off' | 'error' | 'warn' | 'info' | 'debug'
logDir: './logs', // Log file directory
enableGapFilling: false, // Insert silence during network drops (for recording)
protocolDefinitions: {
// Optional overrides for the March 2026 protocol definitions
messageTypes: { STREAM_CLOSE_REQ: 21, STREAM_CLOSE_RESP: 22, VIDEO_SUBSCRIPTION_REQ: 28, VIDEO_SUBSCRIPTION_RESP: 29 },
eventTypes: { PARTICIPANT_VIDEO_ON: 8, PARTICIPANT_VIDEO_OFF: 9 },
mediaDataOptions: { VIDEO_SINGLE_INDIVIDUAL_STREAM: 4 }
}
});See library/README.md for complete documentation including:
- Helper classes (WebhookManager, FrontendWssManager, FrontendManager)
- Utilities (FileLogger, RTMSError, signatureHelper)
- Advanced features (reconnection, state management, gap filling)
- Architecture overview