@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.
- package/dist/index.js +123 -83
- 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
|
-
|
|
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
|
|
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
|
|
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