FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Release 1.0.3 by MaxMansfield · Pull Request #100 · zoom/rtms · GitHub

/ rtms Public
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
10b2cb5
chore: bump version to 1.0.3
MaxMansfield Feb 17, 2026
dc5fa60
fix: treat RTMS_SDK_NOT_EXIST as success in release() (#93)
MaxMansfield Feb 17, 2026
f072b59
fix: prevent default params override when setting individual media pa…
MaxMansfield Feb 17, 2026
a0bb164
fix(js): support pre-parsed req.body in webhook handler for Express c…
MaxMansfield Feb 17, 2026
de26027
fix: validate required 'event' field in webhook payloads (#95)
MaxMansfield Feb 17, 2026
761575a
Merge branch 'fix/94-video-params-order' into dev
MaxMansfield Feb 17, 2026
de1ffe7
Merge branch 'fix/96-webhook-express-body' into dev
MaxMansfield Feb 17, 2026
18c3b55
Merge branch 'fix/95-webhook-schema-validation' into dev
MaxMansfield Feb 17, 2026
d4ca07e
docs: add CHANGELOG entry for 1.0.3
MaxMansfield Feb 17, 2026
bacaaf1
fix: refactor Python event dispatcher and add onMediaConnectionInterr…
MaxMansfield Feb 17, 2026
893d6f2
feat: add webinar_uuid support to join parameters
MaxMansfield Feb 17, 2026
8c3239e
Merge branch 'fix/webinar-uuid-support' into dev
MaxMansfield Feb 17, 2026
50ef43d
docs: update CHANGELOG for event dispatcher and webinar UUID fixes
MaxMansfield Feb 17, 2026
ce03e43
fix: auto-subscribe to participant events in setOnUserUpdate and expo…
MaxMansfield Feb 24, 2026
0f0799e
Merge branch 'fix/user-update-callback' into dev
MaxMansfield Feb 24, 2026
bbb3131
fix: expose constants in python sdk
MaxMansfield Feb 24, 2026
73ec0d6
Merge branch 'fix/user-update-callback' into dev
MaxMansfield Feb 24, 2026
76633a6
fix(docs): make the callout for mixed streams more prominent
MaxMansfield Feb 24, 2026
File filter

Filter by extension

Filter by extension .cpp  (1) .h  (1) .json  (1) .md  (2) .py  (1) .pyi  (1) .toml  (1) .ts  (2) All 8 file types selected
Only manifest files
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
15 changes: 15 additions & 0 deletions CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ All notable changes to the Zoom RTMS SDK will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.3] - 2026-02-17

### Added

- **Webinar support**: Added `webinar_uuid` parameter to `join()` for Zoom Webinar events (`webinar.rtms_started`); priority: `meeting_uuid` > `webinar_uuid` > `session_id`
- **`onMediaConnectionInterrupted` callback**: New high-level callback for `EVENT_MEDIA_CONNECTION_INTERRUPTED` events with auto-subscription (Node.js and Python)

### Fixed

- **Release crash on ended sessions**: `release()` no longer throws when the meeting has already ended externally; cleanup always completes and `RTMS_SDK_NOT_EXIST` is treated as success ([#93](https://github.com/zoom/rtms/issues/93))
- **Video params ignored after audio params**: Calling `setAudioParams()` before `setVideoParams()` no longer overwrites video configuration with defaults; individual param setters skip the default-filling logic ([#94](https://github.com/zoom/rtms/issues/94))
- **Express middleware compatibility**: `createWebhookHandler` now reads from `req.body` when pre-parsed by middleware (e.g., `express.json()`), preventing hangs when the request stream has already been consumed ([#96](https://github.com/zoom/rtms/issues/96))
- **Webhook schema validation**: Webhook handlers (Node.js and Python) now validate that the `event` field is present in the payload and return 400 Bad Request for invalid payloads ([#95](https://github.com/zoom/rtms/issues/95))
- **Python event callback coexistence**: Refactored to shared event dispatcher so `onParticipantEvent`, `onActiveSpeakerEvent`, and `onSharingEvent` can all be registered simultaneously (previously each overwrote the last)

## [1.0.2] - 2026-01-30

### Added
Expand Down
4 changes: 4 additions & 0 deletions README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ For detailed contribution guidelines, build instructions, and troubleshooting, s

## Usage

> **Speaker Identification with Mixed Audio**
>
> When using `AUDIO_MIXED_STREAM` (the default), all participants are mixed into a single audio stream and the audio callback's metadata will **not** identify the current speaker. To identify who is speaking, register an `onActiveSpeakerEvent` callback — it fires whenever the active speaker changes. See [Troubleshooting #7](#7-identifying-speakers-with-mixed-audio-streams) and [#80](https://github.com/zoom/rtms/issues/80) for details.

### Node.js - Webhook Integration

Easily respond to Zoom webhooks and connect to RTMS streams:
Expand Down
81 changes: 68 additions & 13 deletions index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ function exposeNumberConstants(nativeModule: any): Record<string, number> {
'MEDIA_TYPE_TRANSCRIPT', 'MEDIA_TYPE_CHAT', 'MEDIA_TYPE_ALL',
// Session events
'SESSION_EVENT_ADD', 'SESSION_EVENT_STOP', 'SESSION_EVENT_PAUSE', 'SESSION_EVENT_RESUME',
// User events
'USER_JOIN', 'USER_LEAVE',
// Event types (for subscribeEvent/onEventEx)
'EVENT_UNDEFINED', 'EVENT_FIRST_PACKET_TIMESTAMP', 'EVENT_ACTIVE_SPEAKER_CHANGE',
'EVENT_PARTICIPANT_JOIN', 'EVENT_PARTICIPANT_LEAVE',
Expand Down Expand Up @@ -453,23 +455,28 @@ export function createWebhookHandler(callback: WebhookCallbackUnion, path: strin
return;
}

let body = '';
req.on('data', chunk => body += chunk.toString());

req.on('end', () => {
const processPayload = (body: string) => {
try {
Logger.debug('webhook', `Received webhook request: ${req.url}`);
const payload = JSON.parse(body);


// Validate required webhook fields
if (!payload.event || typeof payload.event !== 'string') {
Logger.warn('webhook', 'Received webhook payload missing required "event" field');
res.writeHead(400, headers);
res.end(JSON.stringify({ error: 'Invalid webhook payload: missing required "event" field' }));
return;
}

// Log the webhook event
Logger.info('webhook', `Received event: ${payload.event || 'unknown'}`, {
eventType: payload.event,
payloadSize: body.length
});

// Check if this is a raw webhook callback
const isRawCallback = isRawWebhookCallback(callback);

if (isRawCallback) {
// For raw callbacks, pass req and res objects
process.nextTick(() => {
Expand All @@ -493,7 +500,7 @@ export function createWebhookHandler(callback: WebhookCallbackUnion, path: strin
Logger.error('webhook', `Error in webhook callback: ${err instanceof Error ? err.message : 'Unknown error'}`);
}
});

res.writeHead(200, headers);
res.end(JSON.stringify({ status: 'ok' }));
}
Expand All @@ -502,7 +509,19 @@ export function createWebhookHandler(callback: WebhookCallbackUnion, path: strin
res.writeHead(400, headers);
res.end(JSON.stringify({ error: 'Invalid JSON received' }));
}
});
};

// Check if body was already parsed by middleware (e.g., express.json())
if ((req as any).body !== undefined) {
const body = typeof (req as any).body === 'string'
? (req as any).body
: JSON.stringify((req as any).body);
processPayload(body);
} else {
let body = '';
req.on('data', chunk => body += chunk.toString());
req.on('end', () => processPayload(body));
}
};
}

Expand Down Expand Up @@ -845,6 +864,7 @@ class Client extends nativeRtms.Client {
private activeSpeakerEventCallback: ((timestamp: number, userId: number, userName: string) => void) | null = null;
private sharingEventCallback: ((event: 'start' | 'stop', timestamp: number, userId?: number, userName?: string) => void) | null = null;
private rawEventCallback: ((eventData: string) => void) | null = null;
private mediaConnectionInterruptedCallback: ((timestamp: number) => void) | null = null;
private eventHandlerRegistered: boolean = false;

constructor() {
Expand Down Expand Up @@ -920,6 +940,12 @@ class Client extends nativeRtms.Client {
);
}
break;

case nativeRtms.EVENT_MEDIA_CONNECTION_INTERRUPTED:
if (this.mediaConnectionInterruptedCallback) {
this.mediaConnectionInterruptedCallback(data.timestamp || 0);
}
break;
}
} catch (e) {
Logger.error('client', `Failed to parse event: ${e}`);
Expand Down Expand Up @@ -947,6 +973,7 @@ class Client extends nativeRtms.Client {

const {
meeting_uuid,
webinar_uuid,
session_id,
rtms_stream_id,
server_urls,
Expand All @@ -957,20 +984,20 @@ class Client extends nativeRtms.Client {
pollInterval = 0
} = options;

// Use meeting_uuid for Meeting SDK events, session_id for Video SDK events
const instance_id = meeting_uuid || session_id;
// Use meeting_uuid for Meeting SDK, webinar_uuid for Webinar, session_id for Video SDK
const instance_id = meeting_uuid || webinar_uuid || session_id;

this.pollRate = pollInterval;

Logger.info('client', `Joining ${meeting_uuid ? 'meeting' : 'session'}: ${instance_id}`, {
Logger.info('client', `Joining ${meeting_uuid ? 'meeting' : webinar_uuid ? 'webinar' : 'session'}: ${instance_id}`, {
streamId: rtms_stream_id,
serverUrls: server_urls,
timeout: providedTimeout,
pollInterval
});

if (!instance_id) {
throw new Error('Either meeting_uuid or session_id must be provided');
throw new Error('Either meeting_uuid, webinar_uuid, or session_id must be provided');
}

const finalSignature = providedSignature || generateSignature({
Expand Down Expand Up @@ -1124,6 +1151,34 @@ class Client extends nativeRtms.Client {
return true;
}

/**
* Register a callback for media connection interrupted events
*
* This automatically subscribes to EVENT_MEDIA_CONNECTION_INTERRUPTED.
*
* @param callback Function called when the media connection is interrupted
* @returns true if registration succeeds
*
* @example
* ```typescript
* client.onMediaConnectionInterrupted((timestamp) => {
* console.log(`Media connection interrupted at ${timestamp}`);
* });
* ```
*/
onMediaConnectionInterrupted(callback: (timestamp: number) => void): boolean {
this.mediaConnectionInterruptedCallback = callback;
this.setupEventHandler();

try {
super.subscribeEvent([nativeRtms.EVENT_MEDIA_CONNECTION_INTERRUPTED]);
} catch (e) {
Logger.warn('client', `Failed to auto-subscribe to media connection interrupted events: ${e}`);
}

return true;
}

/**
* Register a callback for raw event data
*
Expand Down
2 changes: 1 addition & 1 deletion package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zoom/rtms",
"version": "1.0.2",
"version": "1.0.3",
"description": "Node.js Wrapper for the Zoom RTMS C SDK",
"main": "./build/Release/index.js",
"types": "rtms.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "scikit_build_core.build"

[project]
name = "rtms"
version = "1.0.2"
version = "1.0.3"
description = "Python bindings for the Zoom RTMS C SDK - Real-Time Media Streaming"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
22 changes: 21 additions & 1 deletion rtms.d.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -305,14 +305,17 @@ export interface DeskshareParams {
* Parameters for joining a Zoom RTMS session
*
* For Meeting SDK events (meeting.rtms_started), use meeting_uuid.
* For Webinar events (webinar.rtms_started), use webinar_uuid.
* For Video SDK events (session.rtms_started), use session_id.
* If both are provided, meeting_uuid takes precedence.
* Priority: meeting_uuid > webinar_uuid > session_id.
*
* @category Common Interfaces
*/
export interface JoinParams {
/** The UUID of the Zoom meeting (for Meeting SDK events) */
meeting_uuid?: string;
/** The UUID of the Zoom webinar (for Webinar events) */
webinar_uuid?: string;
/** The session ID (for Video SDK events) - used when meeting_uuid is not provided */
session_id?: string;
/** The RTMS stream ID for this connection */
Expand Down Expand Up @@ -876,6 +879,23 @@ export class Client {
*/
onSharingEvent(callback: SharingEventCallback): boolean;

/**
* Sets a callback for media connection interrupted events
*
* This automatically subscribes to EVENT_MEDIA_CONNECTION_INTERRUPTED.
*
* @param callback The callback function to invoke with the event timestamp
* @returns true if the callback was set successfully
*
* @example
* ```typescript
* client.onMediaConnectionInterrupted((timestamp) => {
* console.log(`Media connection interrupted at ${timestamp}`);
* });
* ```
*/
onMediaConnectionInterrupted(callback: (timestamp: number) => void): boolean;

/**
* Sets a callback for raw event data
*
Expand Down
51 changes: 33 additions & 18 deletions src/rtms.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -461,33 +461,37 @@ void Client::uninitialize() {
rtms_uninit();
}

void Client::configure(const MediaParams& params, int media_types, bool enable_application_layer_encryption) {
void Client::configure(const MediaParams& params, int media_types, bool enable_application_layer_encryption, bool apply_defaults) {
// Store the media parameters for future use
media_params_ = params;
enabled_media_types_ = media_types;
media_params_updated_ = true;

// If media types are enabled but no params were set, use sensible defaults
// This ensures proper metadata attribution (e.g., AUDIO_MULTI_STREAMS for audio)
if ((media_types & MediaType::AUDIO) && !media_params_.hasAudioParams()) {
// Skip defaults when called from setAudioParams/setVideoParams/setDeskshareParams
// to avoid prematurely filling in default params for other media types
if (apply_defaults) {
if ((media_types & MediaType::AUDIO) && !media_params_.hasAudioParams()) {
#ifdef RTMS_DEBUG
cerr << "[DEBUG CONFIG] Audio enabled but no params set, using defaults (AUDIO_MULTI_STREAMS)" << endl;
cerr << "[DEBUG CONFIG] Audio enabled but no params set, using defaults (AUDIO_MULTI_STREAMS)" << endl;
#endif
media_params_.setAudioParams(AudioParams());
}
media_params_.setAudioParams(AudioParams());
}

if ((media_types & MediaType::VIDEO) && !media_params_.hasVideoParams()) {
if ((media_types & MediaType::VIDEO) && !media_params_.hasVideoParams()) {
#ifdef RTMS_DEBUG
cerr << "[DEBUG CONFIG] Video enabled but no params set, using defaults" << endl;
cerr << "[DEBUG CONFIG] Video enabled but no params set, using defaults" << endl;
#endif
media_params_.setVideoParams(VideoParams());
}
media_params_.setVideoParams(VideoParams());
}

if ((media_types & MediaType::DESKSHARE) && !media_params_.hasDeskshareParams()) {
if ((media_types & MediaType::DESKSHARE) && !media_params_.hasDeskshareParams()) {
#ifdef RTMS_DEBUG
cerr << "[DEBUG CONFIG] Deskshare enabled but no params set, using defaults" << endl;
cerr << "[DEBUG CONFIG] Deskshare enabled but no params set, using defaults" << endl;
#endif
media_params_.setDeskshareParams(DeskshareParams());
media_params_.setDeskshareParams(DeskshareParams());
}
}

if (!media_params_.hasAudioParams() && !media_params_.hasVideoParams() && !media_params_.hasDeskshareParams()) {
Expand Down Expand Up @@ -568,8 +572,11 @@ void Client::setOnSessionUpdate(SessionUpdateFn callback) {
}

void Client::setOnUserUpdate(UserUpdateFn callback) {
lock_guard<mutex> lock(mutex_);
user_update_callback_ = std::move(callback);
{
lock_guard<mutex> lock(mutex_);
user_update_callback_ = std::move(callback);
}
subscribeEvent({EVENT_ACTIVE_SPEAKER_CHANGE, EVENT_PARTICIPANT_JOIN, EVENT_PARTICIPANT_LEAVE});
}

void Client::setOnDeskshareData(DsDataFn callback){
Expand Down Expand Up @@ -674,9 +681,10 @@ void Client::setDeskshareParams(const DeskshareParams& ds_params)
media_params_.setDeskshareParams(ds_params);

// If deskshare is already enabled, reconfigure with the new params
// Pass apply_defaults=false to avoid filling in defaults for other media types
if (enabled_media_types_ & MediaType::DESKSHARE) {
try {
configure(media_params_, enabled_media_types_, false);
configure(media_params_, enabled_media_types_, false, false);
} catch (const Exception& e) {
cerr << "Warning: Failed to reconfigure deskshare params: " << e.what() << endl;
}
Expand All @@ -691,9 +699,10 @@ void Client::setVideoParams(const VideoParams& video_params)
media_params_.setVideoParams(video_params);

// If video is already enabled, reconfigure with the new params
// Pass apply_defaults=false to avoid filling in defaults for other media types
if (enabled_media_types_ & MediaType::VIDEO) {
try {
configure(media_params_, enabled_media_types_, false);
configure(media_params_, enabled_media_types_, false, false);
} catch (const Exception& e) {
cerr << "Warning: Failed to reconfigure video params: " << e.what() << endl;
}
Expand All @@ -708,9 +717,10 @@ void Client::setAudioParams(const AudioParams& audio_params)
media_params_.setAudioParams(audio_params);

// If audio is already enabled, reconfigure with the new params
// Pass apply_defaults=false to avoid filling in defaults for other media types
if (enabled_media_types_ & MediaType::AUDIO) {
try {
configure(media_params_, enabled_media_types_, false);
configure(media_params_, enabled_media_types_, false, false);
} catch (const Exception& e) {
cerr << "Warning: Failed to reconfigure audio params: " << e.what() << endl;
}
Expand Down Expand Up @@ -758,8 +768,8 @@ void Client::poll() {

void Client::release() {
int result = rtms_release(sdk_);
throwIfError(result, "release");

// Always clean up, even if release returned an error
{
lock_guard<mutex> lock(registry_mutex_);
sdk_registry_.erase(sdk_);
Expand All @@ -774,6 +784,11 @@ void Client::release() {
}

sdk_ = nullptr;

// RTMS_SDK_NOT_EXIST means the resource was already released — that's fine
if (result != RTMS_SDK_OK && result != RTMS_SDK_NOT_EXIST) {
throwIfError(result, "release");
}
}

string Client::uuid() const {
Expand Down
2 changes: 1 addition & 1 deletion src/rtms.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ class Client {

static void initialize(const string& ca, int is_verify_cert = 1, const char* agent = nullptr);
static void uninitialize();
void configure(const MediaParams& params, int media_types, bool enable_application_layer_encryption = false);
void configure(const MediaParams& params, int media_types, bool enable_application_layer_encryption = false, bool apply_defaults = true);

void enableVideo(bool enable);
void enableAudio(bool enable);
Expand Down
Loading
Loading

Back | FazBrowse Home | New Git URL