@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.55 → 1.0.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -351,26 +351,48 @@ class MLNoiseSuppressor {
351
351
  this.processingNode = this.audioContext.createScriptProcessor(bufferSize, 1, 1);
352
352
  let frameCount = 0;
353
353
  const startTime = performance.now();
354
+ // Double-buffering for async ML processing
355
+ // We store the PREVIOUS processed result and output it in the NEXT callback
356
+ // This adds one buffer of latency but ensures we never output zeros
357
+ let previousProcessedBuffer = null;
358
+ let processingInFlight = false;
354
359
  // Process audio frames with ML model
355
- this.processingNode.onaudioprocess = async (event) => {
360
+ // IMPORTANT: onaudioprocess is synchronous! We use double-buffering to handle async ML
361
+ this.processingNode.onaudioprocess = (event) => {
356
362
  const inputData = event.inputBuffer.getChannelData(0);
357
363
  const outputData = event.outputBuffer.getChannelData(0);
358
364
  frameCount++;
359
- try {
360
- // Process with BiLSTM model
361
- const processed = await this.processAudio(new Float32Array(inputData));
362
- outputData.set(processed);
363
- // Log performance every ~4 seconds
364
- if (frameCount % 100 === 0) {
365
- const elapsed = (performance.now() - startTime) / 1000;
366
- const fps = frameCount / elapsed;
367
- console.log(`🎤 [ML] BiLSTM: ${frameCount} frames @ ${fps.toFixed(1)} fps`);
368
- }
365
+ // OUTPUT: Use previously processed audio (or passthrough if not ready yet)
366
+ if (previousProcessedBuffer) {
367
+ outputData.set(previousProcessedBuffer);
369
368
  }
370
- catch (error) {
371
- // On error, pass through original audio
369
+ else {
370
+ // First frame or ML not ready - pass through original audio
372
371
  outputData.set(inputData);
373
372
  }
373
+ // PROCESS: Start async ML processing for the NEXT frame
374
+ // Only start new processing if previous one is complete
375
+ if (!processingInFlight) {
376
+ processingInFlight = true;
377
+ const inputCopy = new Float32Array(inputData);
378
+ // Fire-and-forget async processing
379
+ this.processAudio(inputCopy)
380
+ .then((processed) => {
381
+ previousProcessedBuffer = processed;
382
+ processingInFlight = false;
383
+ })
384
+ .catch((error) => {
385
+ // On error, store the original audio for passthrough
386
+ previousProcessedBuffer = inputCopy;
387
+ processingInFlight = false;
388
+ });
389
+ }
390
+ // Log performance every ~4 seconds
391
+ if (frameCount % 100 === 0) {
392
+ const elapsed = (performance.now() - startTime) / 1000;
393
+ const fps = frameCount / elapsed;
394
+ console.log(`🎤 [ML] BiLSTM: ${frameCount} frames @ ${fps.toFixed(1)} fps`);
395
+ }
374
396
  };
375
397
  // Connect: source -> highpass -> BiLSTM processor -> destination
376
398
  source.connect(this.highPassFilter);
@@ -41,7 +41,7 @@ class MediasoupManager {
41
41
  this.recvTransport = null;
42
42
  this.producers = new Map();
43
43
  this.consumers = new Map();
44
- this.participantId = '';
44
+ this.participantId = "";
45
45
  this.socket = socket;
46
46
  this.device = new mediasoupClient.Device();
47
47
  }
@@ -100,7 +100,7 @@ class MediasoupManager {
100
100
  // Emit event so parent SDK can recreate producers
101
101
  this.socket.emit("transport-failed", {
102
102
  participantId: this.participantId,
103
- direction: "send"
103
+ direction: "send",
104
104
  });
105
105
  }
106
106
  });
@@ -126,30 +126,46 @@ class MediasoupManager {
126
126
  async produce(track, appData) {
127
127
  if (!this.sendTransport)
128
128
  throw new Error("Send transport not initialized");
129
+ console.log(`📤 [MediaSoup] Producing ${track.kind} track (transport state: ${this.sendTransport.connectionState})`);
129
130
  // Configure simulcast for video tracks for adaptive bitrate
130
131
  const produceOptions = { track, appData };
131
- if (track.kind === 'video') {
132
+ if (track.kind === "video") {
132
133
  produceOptions.encodings = [
133
134
  // Low quality layer - 100 kbps, good for poor connections
134
- { rid: 'r0', active: true, maxBitrate: 100000, scaleResolutionDownBy: 4 },
135
+ {
136
+ rid: "r0",
137
+ active: true,
138
+ maxBitrate: 100000,
139
+ scaleResolutionDownBy: 4,
140
+ },
135
141
  // Medium quality layer - 300 kbps, balanced
136
- { rid: 'r1', active: true, maxBitrate: 300000, scaleResolutionDownBy: 2 },
142
+ {
143
+ rid: "r1",
144
+ active: true,
145
+ maxBitrate: 300000,
146
+ scaleResolutionDownBy: 2,
147
+ },
137
148
  // High quality layer - 900 kbps, full resolution
138
- { rid: 'r2', active: true, maxBitrate: 900000, scaleResolutionDownBy: 1 }
149
+ {
150
+ rid: "r2",
151
+ active: true,
152
+ maxBitrate: 900000,
153
+ scaleResolutionDownBy: 1,
154
+ },
139
155
  ];
140
156
  // VP8 codec for simulcast support
141
157
  produceOptions.codecOptions = {
142
- videoGoogleStartBitrate: 1000
158
+ videoGoogleStartBitrate: 1000,
143
159
  };
144
160
  }
145
161
  const producer = await this.sendTransport.produce(produceOptions);
146
162
  // Handle producer events
147
- producer.on('transportclose', () => {
163
+ producer.on("transportclose", () => {
148
164
  console.warn(`⚠️ [MediaSoup] Producer ${producer.id.substring(0, 8)} transport closed`);
149
165
  this.producers.delete(producer.id);
150
166
  // The main SDK (index.ts) should handle recreation
151
167
  });
152
- producer.on('trackended', () => {
168
+ producer.on("trackended", () => {
153
169
  console.warn(`⚠️ [MediaSoup] Producer ${producer.id.substring(0, 8)} track ended`);
154
170
  producer.close();
155
171
  this.producers.delete(producer.id);
@@ -167,12 +183,12 @@ class MediasoupManager {
167
183
  rtpParameters: data.rtpParameters,
168
184
  });
169
185
  // Handle consumer events
170
- consumer.on('transportclose', () => {
186
+ consumer.on("transportclose", () => {
171
187
  console.warn(`⚠️ [MediaSoup] Consumer ${consumer.id.substring(0, 8)} transport closed`);
172
188
  this.consumers.delete(consumer.id);
173
189
  // The main SDK (index.ts) should handle recreation
174
190
  });
175
- consumer.on('trackended', () => {
191
+ consumer.on("trackended", () => {
176
192
  console.warn(`⚠️ [MediaSoup] Consumer ${consumer.id.substring(0, 8)} track ended`);
177
193
  consumer.close();
178
194
  this.consumers.delete(consumer.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newgameplusinc/odyssey-audio-video-sdk-dev",
3
- "version": "1.0.55",
3
+ "version": "1.0.57",
4
4
  "description": "Odyssey Spatial Audio & Video SDK using MediaSoup for real-time communication with AI-powered noise suppression",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",