@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.258 → 1.0.260
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/README.md +45 -1
- package/dist/audio/AudioNodeFactory.d.ts +130 -0
- package/dist/audio/AudioNodeFactory.js +158 -0
- package/dist/audio/AudioPipeline.d.ts +89 -0
- package/dist/audio/AudioPipeline.js +138 -0
- package/dist/{MLNoiseSuppressor.d.ts → audio/MLNoiseSuppressor.d.ts} +7 -7
- package/dist/{MLNoiseSuppressor.js → audio/MLNoiseSuppressor.js} +19 -52
- package/dist/audio/index.d.ts +6 -0
- package/dist/audio/index.js +22 -0
- package/dist/channels/huddle/HuddleChannel.d.ts +87 -0
- package/dist/channels/huddle/HuddleChannel.js +152 -0
- package/dist/channels/huddle/HuddleTypes.d.ts +85 -0
- package/dist/channels/huddle/HuddleTypes.js +25 -0
- package/dist/channels/huddle/index.d.ts +5 -0
- package/dist/channels/huddle/index.js +21 -0
- package/dist/channels/index.d.ts +5 -0
- package/dist/channels/index.js +21 -0
- package/dist/channels/spatial/SpatialAudioChannel.d.ts +144 -0
- package/dist/channels/spatial/SpatialAudioChannel.js +476 -0
- package/dist/channels/spatial/SpatialAudioTypes.d.ts +85 -0
- package/dist/channels/spatial/SpatialAudioTypes.js +42 -0
- package/dist/channels/spatial/index.d.ts +5 -0
- package/dist/channels/spatial/index.js +21 -0
- package/dist/{EventManager.d.ts → core/EventManager.d.ts} +4 -2
- package/dist/{EventManager.js → core/EventManager.js} +5 -3
- package/dist/{MediasoupManager.d.ts → core/MediasoupManager.d.ts} +10 -4
- package/dist/{MediasoupManager.js → core/MediasoupManager.js} +56 -44
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +21 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +43 -9
- package/dist/sdk/index.d.ts +36 -0
- package/dist/sdk/index.js +121 -0
- package/dist/types/events.d.ts +154 -0
- package/dist/{types.js → types/events.js} +3 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.js +23 -0
- package/dist/types/participant.d.ts +65 -0
- package/dist/types/participant.js +5 -0
- package/dist/types/position.d.ts +47 -0
- package/dist/types/position.js +9 -0
- package/dist/types/room.d.ts +82 -0
- package/dist/types/room.js +5 -0
- package/dist/utils/audio/clarity-score.d.ts +33 -0
- package/dist/utils/audio/clarity-score.js +81 -0
- package/dist/utils/audio/index.d.ts +5 -0
- package/dist/utils/audio/index.js +21 -0
- package/dist/utils/audio/voice-filter.d.ts +30 -0
- package/dist/utils/audio/voice-filter.js +70 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.js +23 -0
- package/dist/utils/position/coordinates.d.ts +37 -0
- package/dist/utils/position/coordinates.js +61 -0
- package/dist/utils/position/index.d.ts +6 -0
- package/dist/utils/position/index.js +22 -0
- package/dist/utils/position/normalize.d.ts +37 -0
- package/dist/utils/position/normalize.js +78 -0
- package/dist/utils/position/snap.d.ts +51 -0
- package/dist/utils/position/snap.js +81 -0
- package/dist/utils/smoothing/gain-smoothing.d.ts +45 -0
- package/dist/utils/smoothing/gain-smoothing.js +77 -0
- package/dist/utils/smoothing/index.d.ts +5 -0
- package/dist/utils/smoothing/index.js +21 -0
- package/dist/utils/smoothing/pan-smoothing.d.ts +43 -0
- package/dist/utils/smoothing/pan-smoothing.js +85 -0
- package/dist/utils/spatial/angle-calc.d.ts +24 -0
- package/dist/utils/spatial/angle-calc.js +69 -0
- package/dist/utils/spatial/distance-calc.d.ts +33 -0
- package/dist/utils/spatial/distance-calc.js +48 -0
- package/dist/utils/spatial/gain-calc.d.ts +37 -0
- package/dist/utils/spatial/gain-calc.js +52 -0
- package/dist/utils/spatial/head-position.d.ts +32 -0
- package/dist/utils/spatial/head-position.js +76 -0
- package/dist/utils/spatial/index.d.ts +9 -0
- package/dist/utils/spatial/index.js +25 -0
- package/dist/utils/spatial/listener-calc.d.ts +28 -0
- package/dist/utils/spatial/listener-calc.js +74 -0
- package/dist/utils/spatial/pan-calc.d.ts +48 -0
- package/dist/utils/spatial/pan-calc.js +80 -0
- package/package.json +1 -1
- package/dist/SpatialAudioManager.d.ts +0 -271
- package/dist/SpatialAudioManager.js +0 -1512
- package/dist/types.d.ts +0 -73
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* ML Noise Suppressor - TensorFlow.js-based Real-Time Audio Enhancement
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* Integrates trained ML model for noise suppression
|
|
6
|
+
* Falls back to traditional DSP if ML is unavailable
|
|
5
7
|
*/
|
|
6
8
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
9
|
if (k2 === undefined) k2 = k;
|
|
@@ -45,9 +47,9 @@ class MLNoiseSuppressor {
|
|
|
45
47
|
this.config = null;
|
|
46
48
|
this.normStats = null;
|
|
47
49
|
this.isInitialized = false;
|
|
48
|
-
// Temporal smoothing state
|
|
50
|
+
// Temporal smoothing state
|
|
49
51
|
this.prevMask = null;
|
|
50
|
-
this.SMOOTHING_ALPHA = 0.85;
|
|
52
|
+
this.SMOOTHING_ALPHA = 0.85;
|
|
51
53
|
// Voice frequency preservation
|
|
52
54
|
this.VOICE_FUNDAMENTAL_MIN = 80;
|
|
53
55
|
this.VOICE_FUNDAMENTAL_MAX = 500;
|
|
@@ -57,27 +59,15 @@ class MLNoiseSuppressor {
|
|
|
57
59
|
* @param modelUrl Path to model.json file
|
|
58
60
|
*/
|
|
59
61
|
async initialize(modelUrl) {
|
|
60
|
-
//console.log('🤖 [ML] Initializing ML Noise Suppressor...');
|
|
61
62
|
try {
|
|
62
|
-
// Set TensorFlow.js backend
|
|
63
|
+
// Set TensorFlow.js backend
|
|
63
64
|
await tf.setBackend('webgl');
|
|
64
65
|
await tf.ready();
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (modelJson.modelTopology?.config?.layers?.[0]?.config
|
|
71
|
-
?.batch_input_shape === undefined) {
|
|
72
|
-
// Model JSON is missing inputShape - apply fix
|
|
73
|
-
modelJson.modelTopology.config.layers[0].config.batch_input_shape = [
|
|
74
|
-
null,
|
|
75
|
-
257,
|
|
76
|
-
1,
|
|
77
|
-
1,
|
|
78
|
-
];
|
|
79
|
-
}
|
|
80
|
-
this.model = await tf.loadLayersModel(tf.io.fromMemory(modelJson.modelTopology, modelJson.weightsManifest[0].weights));
|
|
66
|
+
// Load model - use standard loadLayersModel instead of fromMemory
|
|
67
|
+
// This handles weight loading automatically
|
|
68
|
+
console.log(`[MLNoiseSuppressor] Loading model from: ${modelUrl}`);
|
|
69
|
+
this.model = await tf.loadLayersModel(modelUrl);
|
|
70
|
+
console.log(`[MLNoiseSuppressor] Model loaded successfully`);
|
|
81
71
|
// Load config
|
|
82
72
|
const baseUrl = modelUrl.substring(0, modelUrl.lastIndexOf('/'));
|
|
83
73
|
const configResponse = await fetch(`${baseUrl}/model_config.json`);
|
|
@@ -88,54 +78,43 @@ class MLNoiseSuppressor {
|
|
|
88
78
|
this.normStats = await normResponse.json();
|
|
89
79
|
}
|
|
90
80
|
catch (e) {
|
|
91
|
-
// No normalization stats found, use defaults
|
|
92
81
|
this.normStats = { mean: 0, std: 1 };
|
|
93
82
|
}
|
|
94
83
|
this.isInitialized = true;
|
|
84
|
+
console.log(`[MLNoiseSuppressor] Initialization complete`);
|
|
95
85
|
}
|
|
96
86
|
catch (error) {
|
|
87
|
+
console.error(`[MLNoiseSuppressor] Initialization failed:`, error);
|
|
97
88
|
throw error;
|
|
98
89
|
}
|
|
99
90
|
}
|
|
100
91
|
/**
|
|
101
92
|
* Process audio buffer through ML model
|
|
102
|
-
* @param inputBuffer Audio samples to process
|
|
103
|
-
* @returns Denoised audio samples
|
|
104
93
|
*/
|
|
105
94
|
async processAudio(inputBuffer) {
|
|
106
95
|
if (!this.isInitialized || !this.model || !this.config || !this.normStats) {
|
|
107
|
-
// Not initialized, return original audio
|
|
108
96
|
return inputBuffer;
|
|
109
97
|
}
|
|
110
98
|
try {
|
|
111
99
|
return await tf.tidy(() => {
|
|
112
|
-
// 1. Extract features (simplified mel-spectrogram approximation)
|
|
113
100
|
const features = this.extractFeatures(inputBuffer);
|
|
114
|
-
// 2. Normalize features
|
|
115
101
|
const normalizedTensor = tf.tensor2d(features);
|
|
116
102
|
const normalized = normalizedTensor
|
|
117
103
|
.sub(this.normStats.mean)
|
|
118
104
|
.div(Math.max(this.normStats.std, 1e-8));
|
|
119
|
-
// 3. Create sequences for LSTM
|
|
120
105
|
const sequences = this.createSequences(normalized);
|
|
121
|
-
// 4. Run ML inference to get noise suppression mask
|
|
122
106
|
const maskTensor = this.model.predict(sequences);
|
|
123
|
-
// 5. Extract mask values
|
|
124
107
|
const maskArray = maskTensor.dataSync();
|
|
125
|
-
// 6. Apply temporal smoothing (critical for quality!)
|
|
126
108
|
const smoothedMask = this.applyTemporalSmoothing(Array.from(maskArray));
|
|
127
|
-
|
|
128
|
-
const enhanced = this.applyMaskToAudio(inputBuffer, smoothedMask);
|
|
129
|
-
return enhanced;
|
|
109
|
+
return this.applyMaskToAudio(inputBuffer, smoothedMask);
|
|
130
110
|
});
|
|
131
111
|
}
|
|
132
112
|
catch (error) {
|
|
133
|
-
// Return original audio on error (graceful degradation)
|
|
134
113
|
return inputBuffer;
|
|
135
114
|
}
|
|
136
115
|
}
|
|
137
116
|
/**
|
|
138
|
-
* Extract audio features
|
|
117
|
+
* Extract audio features
|
|
139
118
|
*/
|
|
140
119
|
extractFeatures(audio) {
|
|
141
120
|
if (!this.config)
|
|
@@ -148,18 +127,15 @@ class MLNoiseSuppressor {
|
|
|
148
127
|
for (let i = 0; i < numFrames; i++) {
|
|
149
128
|
const start = i * hopLength;
|
|
150
129
|
const frame = audio.slice(start, Math.min(start + frameSize, audio.length));
|
|
151
|
-
// Compute simplified mel bins
|
|
152
130
|
const melBins = [];
|
|
153
131
|
for (let j = 0; j < nMels; j++) {
|
|
154
132
|
const binStart = Math.floor((j / nMels) * frame.length);
|
|
155
133
|
const binEnd = Math.floor(((j + 1) / nMels) * frame.length);
|
|
156
|
-
// Compute energy in this bin
|
|
157
134
|
let energy = 0;
|
|
158
135
|
for (let k = binStart; k < binEnd; k++) {
|
|
159
136
|
energy += frame[k] * frame[k];
|
|
160
137
|
}
|
|
161
138
|
energy = Math.sqrt(energy / (binEnd - binStart));
|
|
162
|
-
// Convert to log scale (dB-like)
|
|
163
139
|
const logEnergy = Math.log10(energy + 1e-10) * 10;
|
|
164
140
|
melBins.push(logEnergy);
|
|
165
141
|
}
|
|
@@ -178,51 +154,42 @@ class MLNoiseSuppressor {
|
|
|
178
154
|
const numFrames = shape[0];
|
|
179
155
|
const numFeatures = shape[1];
|
|
180
156
|
if (numFrames < seqLength) {
|
|
181
|
-
// Pad if needed
|
|
182
157
|
const padding = tf.zeros([seqLength - numFrames, numFeatures]);
|
|
183
158
|
const padded = tf.concat([featureTensor, padding], 0);
|
|
184
159
|
return padded.expandDims(0);
|
|
185
160
|
}
|
|
186
|
-
// Take the last seqLength frames
|
|
187
161
|
const sequence = featureTensor.slice([numFrames - seqLength, 0], [seqLength, numFeatures]);
|
|
188
162
|
return sequence.expandDims(0);
|
|
189
163
|
}
|
|
190
164
|
/**
|
|
191
|
-
* Apply temporal smoothing to mask
|
|
165
|
+
* Apply temporal smoothing to mask
|
|
192
166
|
*/
|
|
193
167
|
applyTemporalSmoothing(currentMask) {
|
|
194
168
|
const smoothed = new Float32Array(currentMask.length);
|
|
195
169
|
if (!this.prevMask || this.prevMask.length !== currentMask.length) {
|
|
196
|
-
// First frame - no smoothing
|
|
197
170
|
this.prevMask = new Float32Array(currentMask);
|
|
198
171
|
return this.prevMask;
|
|
199
172
|
}
|
|
200
|
-
// Exponential moving average
|
|
201
173
|
for (let i = 0; i < currentMask.length; i++) {
|
|
202
174
|
smoothed[i] =
|
|
203
175
|
this.SMOOTHING_ALPHA * currentMask[i] +
|
|
204
176
|
(1 - this.SMOOTHING_ALPHA) * this.prevMask[i];
|
|
205
|
-
// Clamp to valid range [0.05, 1.0]
|
|
206
|
-
// Never completely mute (min 5% to preserve voice quality)
|
|
207
177
|
smoothed[i] = Math.max(0.05, Math.min(1.0, smoothed[i]));
|
|
208
178
|
}
|
|
209
179
|
this.prevMask = smoothed;
|
|
210
180
|
return smoothed;
|
|
211
181
|
}
|
|
212
182
|
/**
|
|
213
|
-
* Apply noise suppression mask to audio
|
|
183
|
+
* Apply noise suppression mask to audio
|
|
214
184
|
*/
|
|
215
185
|
applyMaskToAudio(audio, mask) {
|
|
216
186
|
const output = new Float32Array(audio.length);
|
|
217
|
-
// Apply mask with simple interpolation
|
|
218
187
|
for (let i = 0; i < audio.length; i++) {
|
|
219
|
-
// Map audio sample to mask index
|
|
220
188
|
const maskIdx = Math.floor((i / audio.length) * mask.length);
|
|
221
189
|
const gain = mask[Math.min(maskIdx, mask.length - 1)];
|
|
222
|
-
// Apply gain with voice frequency boost
|
|
223
190
|
output[i] = audio[i] * gain;
|
|
224
191
|
}
|
|
225
|
-
//
|
|
192
|
+
// Fade edges
|
|
226
193
|
const fadeLength = Math.min(128, output.length / 20);
|
|
227
194
|
for (let i = 0; i < fadeLength; i++) {
|
|
228
195
|
const fade = i / fadeLength;
|
|
@@ -234,7 +201,7 @@ class MLNoiseSuppressor {
|
|
|
234
201
|
return output;
|
|
235
202
|
}
|
|
236
203
|
/**
|
|
237
|
-
* Reset processing state
|
|
204
|
+
* Reset processing state
|
|
238
205
|
*/
|
|
239
206
|
reset() {
|
|
240
207
|
this.prevMask = null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Audio module exports
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
__exportStar(require("./AudioPipeline"), exports);
|
|
21
|
+
__exportStar(require("./AudioNodeFactory"), exports);
|
|
22
|
+
__exportStar(require("./MLNoiseSuppressor"), exports);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Huddle Channel
|
|
3
|
+
*
|
|
4
|
+
* Manages private huddle conversations between participants
|
|
5
|
+
*/
|
|
6
|
+
import { Socket } from 'socket.io-client';
|
|
7
|
+
import { HuddleAcceptResponse, HuddleRejectResponse, HuddleResponse } from './HuddleTypes';
|
|
8
|
+
/**
|
|
9
|
+
* Huddle channel configuration
|
|
10
|
+
*/
|
|
11
|
+
export interface HuddleChannelConfig {
|
|
12
|
+
socket: Socket;
|
|
13
|
+
localParticipantId: string;
|
|
14
|
+
roomId: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Huddle Channel class
|
|
18
|
+
* Handles all huddle-related socket communication
|
|
19
|
+
*/
|
|
20
|
+
export declare class HuddleChannel {
|
|
21
|
+
private socket;
|
|
22
|
+
private localParticipantId;
|
|
23
|
+
private roomId;
|
|
24
|
+
private currentChannel;
|
|
25
|
+
constructor(config: HuddleChannelConfig);
|
|
26
|
+
/**
|
|
27
|
+
* Get current channel ID
|
|
28
|
+
*/
|
|
29
|
+
getCurrentChannel(): string;
|
|
30
|
+
/**
|
|
31
|
+
* Set current channel (called when channel changes)
|
|
32
|
+
*/
|
|
33
|
+
setCurrentChannel(channelId: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Check if currently in a huddle
|
|
36
|
+
*/
|
|
37
|
+
isInHuddle(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Send huddle invite to another participant
|
|
40
|
+
*/
|
|
41
|
+
sendInvite(toParticipantId: string): Promise<{
|
|
42
|
+
success?: boolean;
|
|
43
|
+
inviteId?: string;
|
|
44
|
+
huddleId?: string;
|
|
45
|
+
error?: string;
|
|
46
|
+
}>;
|
|
47
|
+
/**
|
|
48
|
+
* Accept a huddle invite
|
|
49
|
+
*/
|
|
50
|
+
acceptInvite(inviteId: string): Promise<HuddleAcceptResponse>;
|
|
51
|
+
/**
|
|
52
|
+
* Reject a huddle invite
|
|
53
|
+
*/
|
|
54
|
+
rejectInvite(inviteId: string): Promise<HuddleRejectResponse>;
|
|
55
|
+
/**
|
|
56
|
+
* Join the global huddle channel
|
|
57
|
+
*/
|
|
58
|
+
join(): Promise<{
|
|
59
|
+
success?: boolean;
|
|
60
|
+
channelId?: string;
|
|
61
|
+
error?: string;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Leave current huddle and return to spatial audio
|
|
65
|
+
*/
|
|
66
|
+
leave(): Promise<{
|
|
67
|
+
success?: boolean;
|
|
68
|
+
channelId?: string;
|
|
69
|
+
error?: string;
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* Get a participant's current channel
|
|
73
|
+
*/
|
|
74
|
+
getParticipantChannel(participantId: string): Promise<string>;
|
|
75
|
+
/**
|
|
76
|
+
* Request another participant to mute (owner only)
|
|
77
|
+
*/
|
|
78
|
+
muteParticipant(participantId: string, requesterId: string): Promise<HuddleResponse>;
|
|
79
|
+
/**
|
|
80
|
+
* Update local participant ID (after reconnection)
|
|
81
|
+
*/
|
|
82
|
+
updateLocalParticipantId(participantId: string): void;
|
|
83
|
+
/**
|
|
84
|
+
* Update room ID (after room change)
|
|
85
|
+
*/
|
|
86
|
+
updateRoomId(roomId: string): void;
|
|
87
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Huddle Channel
|
|
4
|
+
*
|
|
5
|
+
* Manages private huddle conversations between participants
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.HuddleChannel = void 0;
|
|
9
|
+
const HuddleTypes_1 = require("./HuddleTypes");
|
|
10
|
+
/**
|
|
11
|
+
* Huddle Channel class
|
|
12
|
+
* Handles all huddle-related socket communication
|
|
13
|
+
*/
|
|
14
|
+
class HuddleChannel {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.currentChannel = HuddleTypes_1.SPATIAL_CHANNEL_ID;
|
|
17
|
+
this.socket = config.socket;
|
|
18
|
+
this.localParticipantId = config.localParticipantId;
|
|
19
|
+
this.roomId = config.roomId;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get current channel ID
|
|
23
|
+
*/
|
|
24
|
+
getCurrentChannel() {
|
|
25
|
+
return this.currentChannel;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Set current channel (called when channel changes)
|
|
29
|
+
*/
|
|
30
|
+
setCurrentChannel(channelId) {
|
|
31
|
+
this.currentChannel = channelId;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if currently in a huddle
|
|
35
|
+
*/
|
|
36
|
+
isInHuddle() {
|
|
37
|
+
return this.currentChannel !== HuddleTypes_1.SPATIAL_CHANNEL_ID;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Send huddle invite to another participant
|
|
41
|
+
*/
|
|
42
|
+
async sendInvite(toParticipantId) {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
this.socket.emit('send-huddle-invite', {
|
|
45
|
+
fromParticipantId: this.localParticipantId,
|
|
46
|
+
toParticipantId,
|
|
47
|
+
roomId: this.roomId,
|
|
48
|
+
}, (response) => {
|
|
49
|
+
resolve(response);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Accept a huddle invite
|
|
55
|
+
*/
|
|
56
|
+
async acceptInvite(inviteId) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
this.socket.emit('accept-huddle-invite', {
|
|
59
|
+
inviteId,
|
|
60
|
+
participantId: this.localParticipantId,
|
|
61
|
+
}, (response) => {
|
|
62
|
+
if (response.success) {
|
|
63
|
+
// Server may send huddleId OR channelId - handle both
|
|
64
|
+
this.currentChannel = response.channelId || response.huddleId || 'odyssey-huddle';
|
|
65
|
+
}
|
|
66
|
+
resolve(response);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Reject a huddle invite
|
|
72
|
+
*/
|
|
73
|
+
async rejectInvite(inviteId) {
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
this.socket.emit('reject-huddle-invite', {
|
|
76
|
+
inviteId,
|
|
77
|
+
participantId: this.localParticipantId,
|
|
78
|
+
}, (response) => {
|
|
79
|
+
resolve(response);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Join the global huddle channel
|
|
85
|
+
*/
|
|
86
|
+
async join() {
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
this.socket.emit('join-huddle', {
|
|
89
|
+
participantId: this.localParticipantId,
|
|
90
|
+
roomId: this.roomId,
|
|
91
|
+
}, (response) => {
|
|
92
|
+
if (response.success && response.channelId) {
|
|
93
|
+
this.currentChannel = response.channelId;
|
|
94
|
+
}
|
|
95
|
+
resolve(response);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Leave current huddle and return to spatial audio
|
|
101
|
+
*/
|
|
102
|
+
async leave() {
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
this.socket.emit('leave-huddle', {
|
|
105
|
+
participantId: this.localParticipantId,
|
|
106
|
+
roomId: this.roomId,
|
|
107
|
+
}, (response) => {
|
|
108
|
+
if (response.success) {
|
|
109
|
+
this.currentChannel = response.channelId || HuddleTypes_1.SPATIAL_CHANNEL_ID;
|
|
110
|
+
}
|
|
111
|
+
resolve(response);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get a participant's current channel
|
|
117
|
+
*/
|
|
118
|
+
async getParticipantChannel(participantId) {
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
this.socket.emit('get-participant-channel', { participantId }, (response) => {
|
|
121
|
+
resolve(response.channelId || HuddleTypes_1.SPATIAL_CHANNEL_ID);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Request another participant to mute (owner only)
|
|
127
|
+
*/
|
|
128
|
+
async muteParticipant(participantId, requesterId) {
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
this.socket.emit('mute-participant', {
|
|
131
|
+
targetParticipantId: participantId,
|
|
132
|
+
roomId: this.roomId,
|
|
133
|
+
requesterId,
|
|
134
|
+
}, (response) => {
|
|
135
|
+
resolve(response);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Update local participant ID (after reconnection)
|
|
141
|
+
*/
|
|
142
|
+
updateLocalParticipantId(participantId) {
|
|
143
|
+
this.localParticipantId = participantId;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Update room ID (after room change)
|
|
147
|
+
*/
|
|
148
|
+
updateRoomId(roomId) {
|
|
149
|
+
this.roomId = roomId;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
exports.HuddleChannel = HuddleChannel;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Huddle Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for huddle (private conversation) functionality
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Spatial channel ID constant
|
|
8
|
+
*/
|
|
9
|
+
export declare const SPATIAL_CHANNEL_ID = "spatial";
|
|
10
|
+
/**
|
|
11
|
+
* Huddle channel ID prefix
|
|
12
|
+
*/
|
|
13
|
+
export declare const HUDDLE_CHANNEL_PREFIX = "odyssey-huddle";
|
|
14
|
+
/**
|
|
15
|
+
* Check if a channel ID is a huddle channel
|
|
16
|
+
*/
|
|
17
|
+
export declare function isHuddleChannel(channelId?: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Huddle invite data
|
|
20
|
+
*/
|
|
21
|
+
export interface HuddleInvite {
|
|
22
|
+
inviteId: string;
|
|
23
|
+
fromParticipantId: string;
|
|
24
|
+
toParticipantId: string;
|
|
25
|
+
roomId: string;
|
|
26
|
+
huddleId?: string;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
fromUserName?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Huddle response data
|
|
32
|
+
*/
|
|
33
|
+
export interface HuddleResponse {
|
|
34
|
+
success: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Huddle accept response
|
|
39
|
+
*/
|
|
40
|
+
export interface HuddleAcceptResponse extends HuddleResponse {
|
|
41
|
+
huddleId?: string;
|
|
42
|
+
channelId?: string;
|
|
43
|
+
participants?: string[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Huddle reject response
|
|
47
|
+
*/
|
|
48
|
+
export interface HuddleRejectResponse extends HuddleResponse {
|
|
49
|
+
inviteId?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Huddle started event data
|
|
53
|
+
*/
|
|
54
|
+
export interface HuddleStartedEvent {
|
|
55
|
+
huddleId: string;
|
|
56
|
+
channelId: string;
|
|
57
|
+
participants: string[];
|
|
58
|
+
initiatorId: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Huddle updated event data
|
|
62
|
+
*/
|
|
63
|
+
export interface HuddleUpdatedEvent {
|
|
64
|
+
huddleId: string;
|
|
65
|
+
channelId: string;
|
|
66
|
+
participants: string[];
|
|
67
|
+
action: 'joined' | 'left';
|
|
68
|
+
participantId: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Huddle ended event data
|
|
72
|
+
*/
|
|
73
|
+
export interface HuddleEndedEvent {
|
|
74
|
+
huddleId: string;
|
|
75
|
+
channelId: string;
|
|
76
|
+
reason: 'all_left' | 'timeout' | 'forced';
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Participant channel changed event data
|
|
80
|
+
*/
|
|
81
|
+
export interface ParticipantChannelChangedEvent {
|
|
82
|
+
participantId: string;
|
|
83
|
+
channelId: string;
|
|
84
|
+
previousChannel?: string;
|
|
85
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Huddle Types
|
|
4
|
+
*
|
|
5
|
+
* Type definitions for huddle (private conversation) functionality
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.HUDDLE_CHANNEL_PREFIX = exports.SPATIAL_CHANNEL_ID = void 0;
|
|
9
|
+
exports.isHuddleChannel = isHuddleChannel;
|
|
10
|
+
/**
|
|
11
|
+
* Spatial channel ID constant
|
|
12
|
+
*/
|
|
13
|
+
exports.SPATIAL_CHANNEL_ID = 'spatial';
|
|
14
|
+
/**
|
|
15
|
+
* Huddle channel ID prefix
|
|
16
|
+
*/
|
|
17
|
+
exports.HUDDLE_CHANNEL_PREFIX = 'odyssey-huddle';
|
|
18
|
+
/**
|
|
19
|
+
* Check if a channel ID is a huddle channel
|
|
20
|
+
*/
|
|
21
|
+
function isHuddleChannel(channelId) {
|
|
22
|
+
if (!channelId)
|
|
23
|
+
return false;
|
|
24
|
+
return channelId !== exports.SPATIAL_CHANNEL_ID && channelId.includes('huddle');
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Huddle module - Re-exports huddle channel functionality
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
__exportStar(require("./HuddleTypes"), exports);
|
|
21
|
+
__exportStar(require("./HuddleChannel"), exports);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Channels module - Re-exports all channel functionality
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
__exportStar(require("./huddle"), exports);
|
|
21
|
+
__exportStar(require("./spatial"), exports);
|