@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.34 → 1.0.36

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.
Files changed (2) hide show
  1. package/dist/index.js +123 -83
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -157,6 +157,86 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
157
157
  this.spatialAudioManager.setListenerFromLSD(listenerPos, cameraPos, lookAtPos);
158
158
  }
159
159
  listenForEvents() {
160
+ // CRITICAL: Register all-participants-update listener at initialization
161
+ // This ensures channel data is available when consumers are created
162
+ this.socket.on("all-participants-update", (payload) => {
163
+ if (!this.room) {
164
+ this.room = {
165
+ id: payload.roomId,
166
+ participants: new Map(),
167
+ };
168
+ }
169
+ const activeParticipantIds = new Set();
170
+ for (const snapshot of payload.participants) {
171
+ activeParticipantIds.add(snapshot.participantId);
172
+ let participant = this.room?.participants.get(snapshot.participantId);
173
+ const normalizedPosition = {
174
+ x: snapshot.position?.x ?? 0,
175
+ y: snapshot.position?.y ?? 0,
176
+ z: snapshot.position?.z ?? 0,
177
+ };
178
+ const normalizedDirection = {
179
+ x: snapshot.direction?.x ?? 0,
180
+ y: snapshot.direction?.y ?? 0,
181
+ z: snapshot.direction?.z ?? 1,
182
+ };
183
+ const normalizedMediaState = {
184
+ audio: snapshot.mediaState?.audio ?? false,
185
+ video: snapshot.mediaState?.video ?? false,
186
+ sharescreen: snapshot.mediaState?.sharescreen ?? false,
187
+ };
188
+ if (!participant) {
189
+ participant = {
190
+ participantId: snapshot.participantId,
191
+ userId: snapshot.userId,
192
+ deviceId: snapshot.deviceId,
193
+ isLocal: this.localParticipant?.participantId ===
194
+ snapshot.participantId,
195
+ audioTrack: undefined,
196
+ videoTrack: undefined,
197
+ producers: new Map(),
198
+ consumers: new Map(),
199
+ position: normalizedPosition,
200
+ direction: normalizedDirection,
201
+ mediaState: normalizedMediaState,
202
+ };
203
+ }
204
+ else {
205
+ participant.userId = snapshot.userId;
206
+ participant.deviceId = snapshot.deviceId;
207
+ participant.position = normalizedPosition;
208
+ participant.direction = normalizedDirection;
209
+ participant.mediaState = normalizedMediaState;
210
+ }
211
+ participant.bodyHeight = snapshot.bodyHeight;
212
+ participant.bodyShape = snapshot.bodyShape;
213
+ participant.userName = snapshot.userName;
214
+ participant.userEmail = snapshot.userEmail;
215
+ participant.isLocal =
216
+ this.localParticipant?.participantId === snapshot.participantId;
217
+ // CRITICAL: Store channel data for huddle detection
218
+ participant.currentChannel = snapshot.currentChannel || "spatial";
219
+ this.room?.participants.set(snapshot.participantId, participant);
220
+ if (participant.isLocal) {
221
+ this.localParticipant = participant;
222
+ }
223
+ }
224
+ if (this.room) {
225
+ for (const existingId of Array.from(this.room.participants.keys())) {
226
+ if (!activeParticipantIds.has(existingId)) {
227
+ this.room.participants.delete(existingId);
228
+ }
229
+ }
230
+ }
231
+ const normalizedParticipants = this.room
232
+ ? Array.from(this.room.participants.values())
233
+ : [];
234
+ this.emit("all-participants-update", normalizedParticipants);
235
+ // CRITICAL: After receiving channel data, update ALL mute states
236
+ // This ensures existing audio consumers get the correct mute state
237
+ console.log(`📊 [all-participants-update] Received ${normalizedParticipants.length} participants, updating mute states...`);
238
+ this.updateAllParticipantsMuteState();
239
+ });
160
240
  this.socket.on("new-participant", (participantData) => {
161
241
  if (this.room) {
162
242
  const newParticipant = {
@@ -187,83 +267,7 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
187
267
  });
188
268
  this.socket.on("consumer-created", async (data) => {
189
269
  const { consumer, track } = await this.mediasoupManager.consume(data);
190
- this.socket.on("all-participants-update", (payload) => {
191
- if (!this.room) {
192
- this.room = {
193
- id: payload.roomId,
194
- participants: new Map(),
195
- };
196
- }
197
- const activeParticipantIds = new Set();
198
- for (const snapshot of payload.participants) {
199
- activeParticipantIds.add(snapshot.participantId);
200
- let participant = this.room?.participants.get(snapshot.participantId);
201
- const normalizedPosition = {
202
- x: snapshot.position?.x ?? 0,
203
- y: snapshot.position?.y ?? 0,
204
- z: snapshot.position?.z ?? 0,
205
- };
206
- const normalizedDirection = {
207
- x: snapshot.direction?.x ?? 0,
208
- y: snapshot.direction?.y ?? 0,
209
- z: snapshot.direction?.z ?? 1,
210
- };
211
- const normalizedMediaState = {
212
- audio: snapshot.mediaState?.audio ?? false,
213
- video: snapshot.mediaState?.video ?? false,
214
- sharescreen: snapshot.mediaState?.sharescreen ?? false,
215
- };
216
- if (!participant) {
217
- participant = {
218
- participantId: snapshot.participantId,
219
- userId: snapshot.userId,
220
- deviceId: snapshot.deviceId,
221
- isLocal: this.localParticipant?.participantId ===
222
- snapshot.participantId,
223
- audioTrack: undefined,
224
- videoTrack: undefined,
225
- producers: new Map(),
226
- consumers: new Map(),
227
- position: normalizedPosition,
228
- direction: normalizedDirection,
229
- mediaState: normalizedMediaState,
230
- };
231
- }
232
- else {
233
- participant.userId = snapshot.userId;
234
- participant.deviceId = snapshot.deviceId;
235
- participant.position = normalizedPosition;
236
- participant.direction = normalizedDirection;
237
- participant.mediaState = normalizedMediaState;
238
- }
239
- participant.bodyHeight = snapshot.bodyHeight;
240
- participant.bodyShape = snapshot.bodyShape;
241
- participant.userName = snapshot.userName;
242
- participant.userEmail = snapshot.userEmail;
243
- participant.isLocal =
244
- this.localParticipant?.participantId === snapshot.participantId;
245
- this.room?.participants.set(snapshot.participantId, participant);
246
- if (participant.isLocal) {
247
- this.localParticipant = participant;
248
- }
249
- }
250
- if (this.room) {
251
- for (const existingId of Array.from(this.room.participants.keys())) {
252
- if (!activeParticipantIds.has(existingId)) {
253
- this.room.participants.delete(existingId);
254
- }
255
- }
256
- }
257
- const normalizedParticipants = this.room
258
- ? Array.from(this.room.participants.values())
259
- : [];
260
- this.emit("all-participants-update", normalizedParticipants);
261
- });
262
- // Resume the consumer to start receiving media (non-blocking)
263
- this.mediasoupManager
264
- .resumeConsumer(consumer.id)
265
- .then(() => { })
266
- .catch((err) => { });
270
+ // DON'T resume yet! Resume AFTER setting up the audio pipeline
267
271
  let participant = this.room?.participants.get(data.participantId);
268
272
  // If participant doesn't exist yet, create it with the data from the event
269
273
  if (!participant && this.room) {
@@ -283,6 +287,8 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
283
287
  userName: data.userName,
284
288
  userEmail: data.userEmail,
285
289
  };
290
+ // Default to spatial channel if not specified
291
+ participant.currentChannel = "spatial";
286
292
  this.room.participants.set(data.participantId, participant);
287
293
  }
288
294
  if (participant) {
@@ -311,27 +317,46 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
311
317
  const isLocalParticipant = participant.participantId === this.localParticipant?.participantId;
312
318
  if (isLocalParticipant) {
313
319
  // Do NOT connect this audio to Web Audio API
320
+ console.log(`🚫 Skipping local participant audio setup (prevent loopback)`);
314
321
  return; // Exit early to prevent any audio processing
315
322
  }
316
323
  else {
317
324
  // Check if participant is in huddle - if so, skip spatial audio
318
325
  const participantChannel = participant.currentChannel || "spatial";
319
- const isInHuddle = participantChannel === "odyssey-huddle";
326
+ const myChannel = this.localParticipant?.currentChannel || "spatial";
327
+ const isInHuddle = participantChannel !== "spatial";
328
+ console.log(`🎧 Setting up audio for ${participant.participantId?.substring(0, 8)}`);
329
+ console.log(` My channel: ${myChannel}`);
330
+ console.log(` Their channel: ${participantChannel}`);
320
331
  // Setup spatial audio with full 3D positioning (disabled for huddle users)
321
332
  await this.spatialAudioManager.setupSpatialAudioForParticipant(participant.participantId, track, isInHuddle // Disable spatial audio for huddle users
322
333
  );
323
334
  // CRITICAL: Mute if not in same channel
324
- const myChannel = this.localParticipant?.currentChannel || "spatial";
325
335
  const shouldMute = myChannel !== participantChannel;
336
+ console.log(` Should mute: ${shouldMute} (channels ${myChannel === participantChannel ? 'MATCH' : 'DIFFER'})`);
326
337
  this.spatialAudioManager.setParticipantMuted(participant.participantId, shouldMute);
327
338
  // Only update spatial audio position if NOT in huddle
328
339
  if (!isInHuddle) {
329
340
  this.spatialAudioManager.updateSpatialAudio(participant.participantId, data.position);
330
341
  }
331
342
  }
343
+ // NOW resume the consumer after audio pipeline is ready
344
+ this.mediasoupManager
345
+ .resumeConsumer(consumer.id)
346
+ .then(() => {
347
+ console.log(`✅ Consumer ${consumer.id.substring(0, 8)} resumed for ${participant?.participantId?.substring(0, 8)}`);
348
+ })
349
+ .catch((err) => {
350
+ console.error(`❌ Failed to resume consumer ${consumer.id}:`, err);
351
+ });
332
352
  }
333
353
  else if (track.kind === "video") {
334
354
  participant.videoTrack = track;
355
+ // Resume video consumer immediately (no audio pipeline needed)
356
+ this.mediasoupManager
357
+ .resumeConsumer(consumer.id)
358
+ .then(() => { })
359
+ .catch((err) => { });
335
360
  }
336
361
  this.emit("consumer-created", {
337
362
  participant,
@@ -406,11 +431,12 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
406
431
  this.emit("huddle-ended", data);
407
432
  });
408
433
  this.socket.on("participant-channel-changed", (data) => {
434
+ console.log(`📡 participant-channel-changed: ${data.participantId?.substring(0, 8)} → ${data.channelId}`);
409
435
  const participant = this.room?.participants.get(data.participantId);
410
436
  if (participant) {
411
437
  participant.currentChannel = data.channelId;
412
438
  // Toggle spatial audio based on channel
413
- const isInHuddle = data.channelId === "odyssey-huddle";
439
+ const isInHuddle = data.channelId !== "spatial";
414
440
  // Update spatial audio bypass state for this participant
415
441
  if (participant.audioTrack) {
416
442
  this.spatialAudioManager.setupSpatialAudioForParticipant(participant.participantId, participant.audioTrack, isInHuddle // Disable spatial for huddle, enable for spatial
@@ -419,18 +445,19 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
419
445
  });
420
446
  }
421
447
  // CRITICAL: Mute/unmute based on channel matching
422
- // If I'm in huddle and they're in spatial -> MUTE them
423
- // If I'm in spatial and they're in huddle -> MUTE them
424
- // If we're in the same channel -> UNMUTE them
425
448
  const myChannel = this.localParticipant?.currentChannel || "spatial";
426
449
  const theirChannel = data.channelId || "spatial";
427
450
  const shouldMute = myChannel !== theirChannel;
451
+ console.log(` My channel: ${myChannel}, Their channel: ${theirChannel}`);
452
+ console.log(` ${shouldMute ? '🔇 MUTING' : '🔊 UNMUTING'} ${participant.participantId?.substring(0, 8)}`);
428
453
  this.spatialAudioManager.setParticipantMuted(participant.participantId, shouldMute);
429
454
  }
430
455
  // Update local participant if it's them
431
456
  if (this.localParticipant?.participantId === data.participantId && this.localParticipant !== null) {
457
+ console.log(` This is ME changing channel!`);
432
458
  this.localParticipant.currentChannel = data.channelId;
433
459
  // When LOCAL user changes channel, update ALL other participants' mute state
460
+ console.log(` 🔄 Updating ALL participants mute state`);
434
461
  this.updateAllParticipantsMuteState();
435
462
  }
436
463
  this.emit("participant-channel-changed", data);
@@ -494,6 +521,7 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
494
521
  * Join huddle (anyone can join directly without invite)
495
522
  */
496
523
  async joinHuddle() {
524
+ console.log(`🎧 [joinHuddle] Joining group huddle...`);
497
525
  if (!this.localParticipant || !this.room) {
498
526
  return { success: false, error: "Not in a room" };
499
527
  }
@@ -503,10 +531,14 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
503
531
  roomId: this.room.id,
504
532
  }, (response) => {
505
533
  if (response.success && this.localParticipant) {
534
+ console.log(`✅ [joinHuddle] Success! New channel: ${response.channelId}`);
506
535
  this.localParticipant.currentChannel = response.channelId;
507
536
  // CRITICAL: Update mute state for all participants when joining huddle
508
537
  this.updateAllParticipantsMuteState();
509
538
  }
539
+ else {
540
+ console.error(`❌ [joinHuddle] Failed:`, response.error);
541
+ }
510
542
  resolve(response);
511
543
  });
512
544
  });
@@ -515,6 +547,7 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
515
547
  * Leave current huddle and return to spatial audio
516
548
  */
517
549
  async leaveHuddle() {
550
+ console.log(`🚪 [leaveHuddle] Leaving huddle...`);
518
551
  if (!this.localParticipant || !this.room) {
519
552
  return { success: false, error: "Not in a room" };
520
553
  }
@@ -524,10 +557,14 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
524
557
  roomId: this.room.id,
525
558
  }, (response) => {
526
559
  if (response.success && this.localParticipant) {
560
+ console.log(`✅ [leaveHuddle] Success! New channel: ${response.channelId}`);
527
561
  this.localParticipant.currentChannel = response.channelId;
528
562
  // CRITICAL: Update mute state for all participants when leaving huddle
529
563
  this.updateAllParticipantsMuteState();
530
564
  }
565
+ else {
566
+ console.error(`❌ [leaveHuddle] Failed:`, response.error);
567
+ }
531
568
  resolve(response);
532
569
  });
533
570
  });
@@ -553,6 +590,7 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
553
590
  if (!this.localParticipant || !this.room)
554
591
  return;
555
592
  const myChannel = this.localParticipant.currentChannel || "spatial";
593
+ console.log(`🔄 [updateAllParticipantsMuteState] My channel: ${myChannel}`);
556
594
  this.room.participants.forEach((participant) => {
557
595
  // Skip local participant (never hear yourself)
558
596
  if (participant.participantId === this.localParticipant?.participantId) {
@@ -560,8 +598,10 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
560
598
  }
561
599
  const theirChannel = participant.currentChannel || "spatial";
562
600
  const shouldMute = myChannel !== theirChannel;
601
+ console.log(` ${participant.participantId?.substring(0, 8)}: channel=${theirChannel}, ${shouldMute ? '🔇 MUTE' : '🔊 UNMUTE'}`);
563
602
  this.spatialAudioManager.setParticipantMuted(participant.participantId, shouldMute);
564
603
  });
604
+ console.log(`✅ [updateAllParticipantsMuteState] Complete`);
565
605
  }
566
606
  async muteParticipant(participantId) {
567
607
  if (!this.localParticipant || !this.room) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newgameplusinc/odyssey-audio-video-sdk-dev",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "description": "Odyssey Spatial Audio & Video SDK using MediaSoup for real-time communication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",