@dtelecom/server-sdk-node 0.1.0
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 +131 -0
- package/dist/index.d.ts +871 -0
- package/dist/index.js +2908 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2908 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
AudioFrame: () => AudioFrame,
|
|
34
|
+
AudioSource: () => AudioSource,
|
|
35
|
+
AudioStream: () => AudioStream,
|
|
36
|
+
ConnectionQuality: () => ConnectionQuality,
|
|
37
|
+
DataPacket_Kind: () => DataPacket_Kind,
|
|
38
|
+
DisconnectReason: () => DisconnectReason,
|
|
39
|
+
LocalAudioTrack: () => LocalAudioTrack,
|
|
40
|
+
LocalParticipant: () => LocalParticipant,
|
|
41
|
+
LocalTrackPublication: () => LocalTrackPublication,
|
|
42
|
+
LogLevel: () => LogLevel,
|
|
43
|
+
Participant: () => Participant,
|
|
44
|
+
ParticipantInfo_State: () => ParticipantInfo_State,
|
|
45
|
+
RemoteAudioTrack: () => RemoteAudioTrack,
|
|
46
|
+
RemoteParticipant: () => RemoteParticipant,
|
|
47
|
+
RemoteTrackPublication: () => RemoteTrackPublication,
|
|
48
|
+
Room: () => Room,
|
|
49
|
+
TrackPublication: () => TrackPublication,
|
|
50
|
+
TrackSource: () => TrackSource,
|
|
51
|
+
TrackType: () => TrackType,
|
|
52
|
+
createTextMessage: () => createTextMessage,
|
|
53
|
+
decodeDataPacket: () => decodeDataPacket,
|
|
54
|
+
downsample: () => downsample,
|
|
55
|
+
encodeDataPacket: () => encodeDataPacket,
|
|
56
|
+
resample: () => resample,
|
|
57
|
+
setLogLevel: () => setLogLevel,
|
|
58
|
+
upsample: () => upsample
|
|
59
|
+
});
|
|
60
|
+
module.exports = __toCommonJS(index_exports);
|
|
61
|
+
|
|
62
|
+
// src/utils/events.ts
|
|
63
|
+
var import_events = require("events");
|
|
64
|
+
var TypedEmitter = class {
|
|
65
|
+
emitter = new import_events.EventEmitter();
|
|
66
|
+
constructor() {
|
|
67
|
+
this.emitter.setMaxListeners(50);
|
|
68
|
+
}
|
|
69
|
+
on(event, listener) {
|
|
70
|
+
this.emitter.on(event, listener);
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
once(event, listener) {
|
|
74
|
+
this.emitter.once(event, listener);
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
off(event, listener) {
|
|
78
|
+
this.emitter.off(event, listener);
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
emit(event, ...args) {
|
|
82
|
+
return this.emitter.emit(event, ...args);
|
|
83
|
+
}
|
|
84
|
+
removeAllListeners(event) {
|
|
85
|
+
if (event) {
|
|
86
|
+
this.emitter.removeAllListeners(event);
|
|
87
|
+
} else {
|
|
88
|
+
this.emitter.removeAllListeners();
|
|
89
|
+
}
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
listenerCount(event) {
|
|
93
|
+
return this.emitter.listenerCount(event);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/utils/logger.ts
|
|
98
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
99
|
+
LogLevel2[LogLevel2["TRACE"] = 0] = "TRACE";
|
|
100
|
+
LogLevel2[LogLevel2["DEBUG"] = 1] = "DEBUG";
|
|
101
|
+
LogLevel2[LogLevel2["INFO"] = 2] = "INFO";
|
|
102
|
+
LogLevel2[LogLevel2["WARN"] = 3] = "WARN";
|
|
103
|
+
LogLevel2[LogLevel2["ERROR"] = 4] = "ERROR";
|
|
104
|
+
LogLevel2[LogLevel2["SILENT"] = 5] = "SILENT";
|
|
105
|
+
return LogLevel2;
|
|
106
|
+
})(LogLevel || {});
|
|
107
|
+
var levelNames = {
|
|
108
|
+
[0 /* TRACE */]: "TRACE",
|
|
109
|
+
[1 /* DEBUG */]: "DEBUG",
|
|
110
|
+
[2 /* INFO */]: "INFO",
|
|
111
|
+
[3 /* WARN */]: "WARN",
|
|
112
|
+
[4 /* ERROR */]: "ERROR",
|
|
113
|
+
[5 /* SILENT */]: "SILENT"
|
|
114
|
+
};
|
|
115
|
+
var globalLevel = 2 /* INFO */;
|
|
116
|
+
function setLogLevel(level) {
|
|
117
|
+
globalLevel = level;
|
|
118
|
+
}
|
|
119
|
+
function createLogger(component) {
|
|
120
|
+
const log12 = (level, msg, args) => {
|
|
121
|
+
if (level < globalLevel) return;
|
|
122
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
123
|
+
const prefix = `${ts} [${levelNames[level]}] [${component}]`;
|
|
124
|
+
if (args.length > 0) {
|
|
125
|
+
console.log(prefix, msg, ...args);
|
|
126
|
+
} else {
|
|
127
|
+
console.log(prefix, msg);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
trace: (msg, ...args) => log12(0 /* TRACE */, msg, args),
|
|
132
|
+
debug: (msg, ...args) => log12(1 /* DEBUG */, msg, args),
|
|
133
|
+
info: (msg, ...args) => log12(2 /* INFO */, msg, args),
|
|
134
|
+
warn: (msg, ...args) => log12(3 /* WARN */, msg, args),
|
|
135
|
+
error: (msg, ...args) => log12(4 /* ERROR */, msg, args)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/engine.ts
|
|
140
|
+
var import_werift = require("werift");
|
|
141
|
+
|
|
142
|
+
// src/signal.ts
|
|
143
|
+
var import_ws = __toESM(require("ws"));
|
|
144
|
+
|
|
145
|
+
// src/proto/signal.ts
|
|
146
|
+
var _m0 = __toESM(require("protobufjs/minimal"));
|
|
147
|
+
function writeMessage(writer, fieldNumber, encodeFn) {
|
|
148
|
+
encodeFn(writer.uint32(fieldNumber << 3 | 2).fork()).ldelim();
|
|
149
|
+
}
|
|
150
|
+
function writeString(writer, fieldNumber, value) {
|
|
151
|
+
if (value !== "") {
|
|
152
|
+
writer.uint32(fieldNumber << 3 | 2).string(value);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function writeInt32(writer, fieldNumber, value) {
|
|
156
|
+
if (value !== 0) {
|
|
157
|
+
writer.uint32(fieldNumber << 3 | 0).int32(value);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function writeUint32(writer, fieldNumber, value) {
|
|
161
|
+
if (value !== 0) {
|
|
162
|
+
writer.uint32(fieldNumber << 3 | 0).uint32(value);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function writeBool(writer, fieldNumber, value) {
|
|
166
|
+
if (value) {
|
|
167
|
+
writer.uint32(fieldNumber << 3 | 0).bool(value);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
var SessionDescription = {
|
|
171
|
+
encode(message, writer = _m0.Writer.create()) {
|
|
172
|
+
writeString(writer, 1, message.type);
|
|
173
|
+
writeString(writer, 2, message.sdp);
|
|
174
|
+
return writer;
|
|
175
|
+
},
|
|
176
|
+
decode(input, length) {
|
|
177
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
178
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
179
|
+
const message = { type: "", sdp: "" };
|
|
180
|
+
while (reader.pos < end) {
|
|
181
|
+
const tag = reader.uint32();
|
|
182
|
+
switch (tag >>> 3) {
|
|
183
|
+
case 1:
|
|
184
|
+
message.type = reader.string();
|
|
185
|
+
break;
|
|
186
|
+
case 2:
|
|
187
|
+
message.sdp = reader.string();
|
|
188
|
+
break;
|
|
189
|
+
default:
|
|
190
|
+
reader.skipType(tag & 7);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return message;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
var TrickleRequest = {
|
|
198
|
+
encode(message, writer = _m0.Writer.create()) {
|
|
199
|
+
writeString(writer, 1, message.candidateInit);
|
|
200
|
+
writeInt32(writer, 2, message.target);
|
|
201
|
+
return writer;
|
|
202
|
+
},
|
|
203
|
+
decode(input, length) {
|
|
204
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
205
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
206
|
+
const message = { candidateInit: "", target: 0 };
|
|
207
|
+
while (reader.pos < end) {
|
|
208
|
+
const tag = reader.uint32();
|
|
209
|
+
switch (tag >>> 3) {
|
|
210
|
+
case 1:
|
|
211
|
+
message.candidateInit = reader.string();
|
|
212
|
+
break;
|
|
213
|
+
case 2:
|
|
214
|
+
message.target = reader.int32();
|
|
215
|
+
break;
|
|
216
|
+
default:
|
|
217
|
+
reader.skipType(tag & 7);
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return message;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
var AddTrackRequest = {
|
|
225
|
+
encode(message, writer = _m0.Writer.create()) {
|
|
226
|
+
writeString(writer, 1, message.cid);
|
|
227
|
+
writeString(writer, 2, message.name);
|
|
228
|
+
writeInt32(writer, 3, message.type);
|
|
229
|
+
writeUint32(writer, 4, message.width);
|
|
230
|
+
writeUint32(writer, 5, message.height);
|
|
231
|
+
writeBool(writer, 6, message.muted);
|
|
232
|
+
writeBool(writer, 7, message.disableDtx);
|
|
233
|
+
writeInt32(writer, 8, message.source);
|
|
234
|
+
writeString(writer, 10, message.sid);
|
|
235
|
+
return writer;
|
|
236
|
+
},
|
|
237
|
+
decode(input, length) {
|
|
238
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
239
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
240
|
+
const message = {
|
|
241
|
+
cid: "",
|
|
242
|
+
name: "",
|
|
243
|
+
type: 0,
|
|
244
|
+
width: 0,
|
|
245
|
+
height: 0,
|
|
246
|
+
muted: false,
|
|
247
|
+
disableDtx: false,
|
|
248
|
+
source: 0,
|
|
249
|
+
layers: [],
|
|
250
|
+
sid: ""
|
|
251
|
+
};
|
|
252
|
+
while (reader.pos < end) {
|
|
253
|
+
const tag = reader.uint32();
|
|
254
|
+
switch (tag >>> 3) {
|
|
255
|
+
case 1:
|
|
256
|
+
message.cid = reader.string();
|
|
257
|
+
break;
|
|
258
|
+
case 2:
|
|
259
|
+
message.name = reader.string();
|
|
260
|
+
break;
|
|
261
|
+
case 3:
|
|
262
|
+
message.type = reader.int32();
|
|
263
|
+
break;
|
|
264
|
+
case 4:
|
|
265
|
+
message.width = reader.uint32();
|
|
266
|
+
break;
|
|
267
|
+
case 5:
|
|
268
|
+
message.height = reader.uint32();
|
|
269
|
+
break;
|
|
270
|
+
case 6:
|
|
271
|
+
message.muted = reader.bool();
|
|
272
|
+
break;
|
|
273
|
+
case 7:
|
|
274
|
+
message.disableDtx = reader.bool();
|
|
275
|
+
break;
|
|
276
|
+
case 8:
|
|
277
|
+
message.source = reader.int32();
|
|
278
|
+
break;
|
|
279
|
+
case 10:
|
|
280
|
+
message.sid = reader.string();
|
|
281
|
+
break;
|
|
282
|
+
default:
|
|
283
|
+
reader.skipType(tag & 7);
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return message;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var MuteTrackRequest = {
|
|
291
|
+
encode(message, writer = _m0.Writer.create()) {
|
|
292
|
+
writeString(writer, 1, message.sid);
|
|
293
|
+
writeBool(writer, 2, message.muted);
|
|
294
|
+
return writer;
|
|
295
|
+
},
|
|
296
|
+
decode(input, length) {
|
|
297
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
298
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
299
|
+
const message = { sid: "", muted: false };
|
|
300
|
+
while (reader.pos < end) {
|
|
301
|
+
const tag = reader.uint32();
|
|
302
|
+
switch (tag >>> 3) {
|
|
303
|
+
case 1:
|
|
304
|
+
message.sid = reader.string();
|
|
305
|
+
break;
|
|
306
|
+
case 2:
|
|
307
|
+
message.muted = reader.bool();
|
|
308
|
+
break;
|
|
309
|
+
default:
|
|
310
|
+
reader.skipType(tag & 7);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return message;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
var LeaveRequest = {
|
|
318
|
+
encode(message, writer = _m0.Writer.create()) {
|
|
319
|
+
writeBool(writer, 1, message.canReconnect);
|
|
320
|
+
writeInt32(writer, 2, message.reason);
|
|
321
|
+
return writer;
|
|
322
|
+
},
|
|
323
|
+
decode(input, length) {
|
|
324
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
325
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
326
|
+
const message = { canReconnect: false, reason: 0 };
|
|
327
|
+
while (reader.pos < end) {
|
|
328
|
+
const tag = reader.uint32();
|
|
329
|
+
switch (tag >>> 3) {
|
|
330
|
+
case 1:
|
|
331
|
+
message.canReconnect = reader.bool();
|
|
332
|
+
break;
|
|
333
|
+
case 2:
|
|
334
|
+
message.reason = reader.int32();
|
|
335
|
+
break;
|
|
336
|
+
default:
|
|
337
|
+
reader.skipType(tag & 7);
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return message;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
var UpdateSubscription = {
|
|
345
|
+
encode(message, writer = _m0.Writer.create()) {
|
|
346
|
+
for (const v of message.trackSids) {
|
|
347
|
+
writeString(writer, 1, v);
|
|
348
|
+
}
|
|
349
|
+
writeBool(writer, 2, message.subscribe);
|
|
350
|
+
return writer;
|
|
351
|
+
},
|
|
352
|
+
decode(input, length) {
|
|
353
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
354
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
355
|
+
const message = { trackSids: [], subscribe: false, participantTracks: [] };
|
|
356
|
+
while (reader.pos < end) {
|
|
357
|
+
const tag = reader.uint32();
|
|
358
|
+
switch (tag >>> 3) {
|
|
359
|
+
case 1:
|
|
360
|
+
message.trackSids.push(reader.string());
|
|
361
|
+
break;
|
|
362
|
+
case 2:
|
|
363
|
+
message.subscribe = reader.bool();
|
|
364
|
+
break;
|
|
365
|
+
default:
|
|
366
|
+
reader.skipType(tag & 7);
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return message;
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
var Room_decode = (reader, length) => {
|
|
374
|
+
const end = reader.pos + length;
|
|
375
|
+
const msg = {
|
|
376
|
+
sid: "",
|
|
377
|
+
name: "",
|
|
378
|
+
emptyTimeout: 0,
|
|
379
|
+
maxParticipants: 0,
|
|
380
|
+
creationTime: 0,
|
|
381
|
+
turnPassword: "",
|
|
382
|
+
enabledCodecs: [],
|
|
383
|
+
metadata: "",
|
|
384
|
+
numParticipants: 0,
|
|
385
|
+
activeRecording: false
|
|
386
|
+
};
|
|
387
|
+
while (reader.pos < end) {
|
|
388
|
+
const tag = reader.uint32();
|
|
389
|
+
switch (tag >>> 3) {
|
|
390
|
+
case 1:
|
|
391
|
+
msg.sid = reader.string();
|
|
392
|
+
break;
|
|
393
|
+
case 2:
|
|
394
|
+
msg.name = reader.string();
|
|
395
|
+
break;
|
|
396
|
+
case 3:
|
|
397
|
+
msg.emptyTimeout = reader.uint32();
|
|
398
|
+
break;
|
|
399
|
+
case 4:
|
|
400
|
+
msg.maxParticipants = reader.uint32();
|
|
401
|
+
break;
|
|
402
|
+
case 5:
|
|
403
|
+
msg.creationTime = reader.int64();
|
|
404
|
+
break;
|
|
405
|
+
case 6:
|
|
406
|
+
msg.turnPassword = reader.string();
|
|
407
|
+
break;
|
|
408
|
+
case 7: {
|
|
409
|
+
const codec = { mime: "", fmtpLine: "" };
|
|
410
|
+
const cEnd = reader.pos + reader.uint32();
|
|
411
|
+
while (reader.pos < cEnd) {
|
|
412
|
+
const cTag = reader.uint32();
|
|
413
|
+
switch (cTag >>> 3) {
|
|
414
|
+
case 1:
|
|
415
|
+
codec.mime = reader.string();
|
|
416
|
+
break;
|
|
417
|
+
case 2:
|
|
418
|
+
codec.fmtpLine = reader.string();
|
|
419
|
+
break;
|
|
420
|
+
default:
|
|
421
|
+
reader.skipType(cTag & 7);
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
msg.enabledCodecs.push(codec);
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
case 8:
|
|
429
|
+
msg.metadata = reader.string();
|
|
430
|
+
break;
|
|
431
|
+
case 9:
|
|
432
|
+
msg.numParticipants = reader.uint32();
|
|
433
|
+
break;
|
|
434
|
+
case 10:
|
|
435
|
+
msg.activeRecording = reader.bool();
|
|
436
|
+
break;
|
|
437
|
+
default:
|
|
438
|
+
reader.skipType(tag & 7);
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return msg;
|
|
443
|
+
};
|
|
444
|
+
var ICEServer_decode = (reader, length) => {
|
|
445
|
+
const end = reader.pos + length;
|
|
446
|
+
const msg = { urls: [], username: "", credential: "" };
|
|
447
|
+
while (reader.pos < end) {
|
|
448
|
+
const tag = reader.uint32();
|
|
449
|
+
switch (tag >>> 3) {
|
|
450
|
+
case 1:
|
|
451
|
+
msg.urls.push(reader.string());
|
|
452
|
+
break;
|
|
453
|
+
case 2:
|
|
454
|
+
msg.username = reader.string();
|
|
455
|
+
break;
|
|
456
|
+
case 3:
|
|
457
|
+
msg.credential = reader.string();
|
|
458
|
+
break;
|
|
459
|
+
default:
|
|
460
|
+
reader.skipType(tag & 7);
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return msg;
|
|
465
|
+
};
|
|
466
|
+
var TrackInfo_decode = (reader, length) => {
|
|
467
|
+
const end = reader.pos + length;
|
|
468
|
+
const msg = {
|
|
469
|
+
sid: "",
|
|
470
|
+
type: 0,
|
|
471
|
+
name: "",
|
|
472
|
+
muted: false,
|
|
473
|
+
width: 0,
|
|
474
|
+
height: 0,
|
|
475
|
+
simulcast: false,
|
|
476
|
+
disableDtx: false,
|
|
477
|
+
source: 0,
|
|
478
|
+
layers: [],
|
|
479
|
+
mimeType: "",
|
|
480
|
+
mid: ""
|
|
481
|
+
};
|
|
482
|
+
while (reader.pos < end) {
|
|
483
|
+
const tag = reader.uint32();
|
|
484
|
+
switch (tag >>> 3) {
|
|
485
|
+
case 1:
|
|
486
|
+
msg.sid = reader.string();
|
|
487
|
+
break;
|
|
488
|
+
case 2:
|
|
489
|
+
msg.type = reader.int32();
|
|
490
|
+
break;
|
|
491
|
+
case 3:
|
|
492
|
+
msg.name = reader.string();
|
|
493
|
+
break;
|
|
494
|
+
case 4:
|
|
495
|
+
msg.muted = reader.bool();
|
|
496
|
+
break;
|
|
497
|
+
case 5:
|
|
498
|
+
msg.width = reader.uint32();
|
|
499
|
+
break;
|
|
500
|
+
case 6:
|
|
501
|
+
msg.height = reader.uint32();
|
|
502
|
+
break;
|
|
503
|
+
case 7:
|
|
504
|
+
msg.simulcast = reader.bool();
|
|
505
|
+
break;
|
|
506
|
+
case 8:
|
|
507
|
+
msg.disableDtx = reader.bool();
|
|
508
|
+
break;
|
|
509
|
+
case 9:
|
|
510
|
+
msg.source = reader.int32();
|
|
511
|
+
break;
|
|
512
|
+
case 11:
|
|
513
|
+
msg.mimeType = reader.string();
|
|
514
|
+
break;
|
|
515
|
+
case 12:
|
|
516
|
+
msg.mid = reader.string();
|
|
517
|
+
break;
|
|
518
|
+
default:
|
|
519
|
+
reader.skipType(tag & 7);
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return msg;
|
|
524
|
+
};
|
|
525
|
+
var ParticipantInfo_decode = (reader, length) => {
|
|
526
|
+
const end = reader.pos + length;
|
|
527
|
+
const msg = {
|
|
528
|
+
sid: "",
|
|
529
|
+
identity: "",
|
|
530
|
+
state: 0,
|
|
531
|
+
tracks: [],
|
|
532
|
+
metadata: "",
|
|
533
|
+
joinedAt: 0,
|
|
534
|
+
name: "",
|
|
535
|
+
version: 0,
|
|
536
|
+
region: "",
|
|
537
|
+
isPublisher: false
|
|
538
|
+
};
|
|
539
|
+
while (reader.pos < end) {
|
|
540
|
+
const tag = reader.uint32();
|
|
541
|
+
switch (tag >>> 3) {
|
|
542
|
+
case 1:
|
|
543
|
+
msg.sid = reader.string();
|
|
544
|
+
break;
|
|
545
|
+
case 2:
|
|
546
|
+
msg.identity = reader.string();
|
|
547
|
+
break;
|
|
548
|
+
case 3:
|
|
549
|
+
msg.state = reader.int32();
|
|
550
|
+
break;
|
|
551
|
+
case 4:
|
|
552
|
+
msg.tracks.push(TrackInfo_decode(reader, reader.uint32()));
|
|
553
|
+
break;
|
|
554
|
+
case 5:
|
|
555
|
+
msg.metadata = reader.string();
|
|
556
|
+
break;
|
|
557
|
+
case 6:
|
|
558
|
+
msg.joinedAt = reader.int64();
|
|
559
|
+
break;
|
|
560
|
+
case 7:
|
|
561
|
+
msg.name = reader.string();
|
|
562
|
+
break;
|
|
563
|
+
case 10:
|
|
564
|
+
msg.version = reader.uint32();
|
|
565
|
+
break;
|
|
566
|
+
case 11: {
|
|
567
|
+
const pEnd = reader.pos + reader.uint32();
|
|
568
|
+
const perm = {
|
|
569
|
+
canSubscribe: false,
|
|
570
|
+
canPublish: false,
|
|
571
|
+
canPublishData: false,
|
|
572
|
+
hidden: false,
|
|
573
|
+
recorder: false
|
|
574
|
+
};
|
|
575
|
+
while (reader.pos < pEnd) {
|
|
576
|
+
const pTag = reader.uint32();
|
|
577
|
+
switch (pTag >>> 3) {
|
|
578
|
+
case 1:
|
|
579
|
+
perm.canSubscribe = reader.bool();
|
|
580
|
+
break;
|
|
581
|
+
case 2:
|
|
582
|
+
perm.canPublish = reader.bool();
|
|
583
|
+
break;
|
|
584
|
+
case 3:
|
|
585
|
+
perm.canPublishData = reader.bool();
|
|
586
|
+
break;
|
|
587
|
+
case 7:
|
|
588
|
+
perm.hidden = reader.bool();
|
|
589
|
+
break;
|
|
590
|
+
case 8:
|
|
591
|
+
perm.recorder = reader.bool();
|
|
592
|
+
break;
|
|
593
|
+
default:
|
|
594
|
+
reader.skipType(pTag & 7);
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
msg.permission = perm;
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
case 12:
|
|
602
|
+
msg.region = reader.string();
|
|
603
|
+
break;
|
|
604
|
+
case 13:
|
|
605
|
+
msg.isPublisher = reader.bool();
|
|
606
|
+
break;
|
|
607
|
+
default:
|
|
608
|
+
reader.skipType(tag & 7);
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return msg;
|
|
613
|
+
};
|
|
614
|
+
var SpeakerInfo_decode = (reader, length) => {
|
|
615
|
+
const end = reader.pos + length;
|
|
616
|
+
const msg = { sid: "", level: 0, active: false };
|
|
617
|
+
while (reader.pos < end) {
|
|
618
|
+
const tag = reader.uint32();
|
|
619
|
+
switch (tag >>> 3) {
|
|
620
|
+
case 1:
|
|
621
|
+
msg.sid = reader.string();
|
|
622
|
+
break;
|
|
623
|
+
case 2:
|
|
624
|
+
msg.level = reader.float();
|
|
625
|
+
break;
|
|
626
|
+
case 3:
|
|
627
|
+
msg.active = reader.bool();
|
|
628
|
+
break;
|
|
629
|
+
default:
|
|
630
|
+
reader.skipType(tag & 7);
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return msg;
|
|
635
|
+
};
|
|
636
|
+
var JoinResponse = {
|
|
637
|
+
decode(input, length) {
|
|
638
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
639
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
640
|
+
const message = {
|
|
641
|
+
otherParticipants: [],
|
|
642
|
+
serverVersion: "",
|
|
643
|
+
iceServers: [],
|
|
644
|
+
subscriberPrimary: false,
|
|
645
|
+
alternativeUrl: "",
|
|
646
|
+
serverRegion: "",
|
|
647
|
+
pingTimeout: 0,
|
|
648
|
+
pingInterval: 0
|
|
649
|
+
};
|
|
650
|
+
while (reader.pos < end) {
|
|
651
|
+
const tag = reader.uint32();
|
|
652
|
+
switch (tag >>> 3) {
|
|
653
|
+
case 1:
|
|
654
|
+
message.room = Room_decode(reader, reader.uint32());
|
|
655
|
+
break;
|
|
656
|
+
case 2:
|
|
657
|
+
message.participant = ParticipantInfo_decode(reader, reader.uint32());
|
|
658
|
+
break;
|
|
659
|
+
case 3:
|
|
660
|
+
message.otherParticipants.push(ParticipantInfo_decode(reader, reader.uint32()));
|
|
661
|
+
break;
|
|
662
|
+
case 4:
|
|
663
|
+
message.serverVersion = reader.string();
|
|
664
|
+
break;
|
|
665
|
+
case 5:
|
|
666
|
+
message.iceServers.push(ICEServer_decode(reader, reader.uint32()));
|
|
667
|
+
break;
|
|
668
|
+
case 6:
|
|
669
|
+
message.subscriberPrimary = reader.bool();
|
|
670
|
+
break;
|
|
671
|
+
case 7:
|
|
672
|
+
message.alternativeUrl = reader.string();
|
|
673
|
+
break;
|
|
674
|
+
case 9:
|
|
675
|
+
message.serverRegion = reader.string();
|
|
676
|
+
break;
|
|
677
|
+
case 10:
|
|
678
|
+
message.pingTimeout = reader.int32();
|
|
679
|
+
break;
|
|
680
|
+
case 11:
|
|
681
|
+
message.pingInterval = reader.int32();
|
|
682
|
+
break;
|
|
683
|
+
default:
|
|
684
|
+
reader.skipType(tag & 7);
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return message;
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
var TrackPublishedResponse = {
|
|
692
|
+
decode(input, length) {
|
|
693
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
694
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
695
|
+
const message = { cid: "" };
|
|
696
|
+
while (reader.pos < end) {
|
|
697
|
+
const tag = reader.uint32();
|
|
698
|
+
switch (tag >>> 3) {
|
|
699
|
+
case 1:
|
|
700
|
+
message.cid = reader.string();
|
|
701
|
+
break;
|
|
702
|
+
case 2:
|
|
703
|
+
message.track = TrackInfo_decode(reader, reader.uint32());
|
|
704
|
+
break;
|
|
705
|
+
default:
|
|
706
|
+
reader.skipType(tag & 7);
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return message;
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
var ParticipantUpdate = {
|
|
714
|
+
decode(input, length) {
|
|
715
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
716
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
717
|
+
const message = { participants: [] };
|
|
718
|
+
while (reader.pos < end) {
|
|
719
|
+
const tag = reader.uint32();
|
|
720
|
+
switch (tag >>> 3) {
|
|
721
|
+
case 1:
|
|
722
|
+
message.participants.push(ParticipantInfo_decode(reader, reader.uint32()));
|
|
723
|
+
break;
|
|
724
|
+
default:
|
|
725
|
+
reader.skipType(tag & 7);
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return message;
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
var SpeakersChanged = {
|
|
733
|
+
decode(input, length) {
|
|
734
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
735
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
736
|
+
const message = { speakers: [] };
|
|
737
|
+
while (reader.pos < end) {
|
|
738
|
+
const tag = reader.uint32();
|
|
739
|
+
switch (tag >>> 3) {
|
|
740
|
+
case 1:
|
|
741
|
+
message.speakers.push(SpeakerInfo_decode(reader, reader.uint32()));
|
|
742
|
+
break;
|
|
743
|
+
default:
|
|
744
|
+
reader.skipType(tag & 7);
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return message;
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
var RoomUpdate = {
|
|
752
|
+
decode(input, length) {
|
|
753
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
754
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
755
|
+
const message = {};
|
|
756
|
+
while (reader.pos < end) {
|
|
757
|
+
const tag = reader.uint32();
|
|
758
|
+
switch (tag >>> 3) {
|
|
759
|
+
case 1:
|
|
760
|
+
message.room = Room_decode(reader, reader.uint32());
|
|
761
|
+
break;
|
|
762
|
+
default:
|
|
763
|
+
reader.skipType(tag & 7);
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return message;
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
var TrackUnpublishedResponse = {
|
|
771
|
+
decode(input, length) {
|
|
772
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
773
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
774
|
+
const message = { trackSid: "" };
|
|
775
|
+
while (reader.pos < end) {
|
|
776
|
+
const tag = reader.uint32();
|
|
777
|
+
switch (tag >>> 3) {
|
|
778
|
+
case 1:
|
|
779
|
+
message.trackSid = reader.string();
|
|
780
|
+
break;
|
|
781
|
+
default:
|
|
782
|
+
reader.skipType(tag & 7);
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return message;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
var ConnectionQualityUpdate = {
|
|
790
|
+
decode(input, length) {
|
|
791
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
792
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
793
|
+
const message = { updates: [] };
|
|
794
|
+
while (reader.pos < end) {
|
|
795
|
+
const tag = reader.uint32();
|
|
796
|
+
switch (tag >>> 3) {
|
|
797
|
+
case 1: {
|
|
798
|
+
const cEnd = reader.pos + reader.uint32();
|
|
799
|
+
const info = { participantSid: "", quality: 0, score: 0 };
|
|
800
|
+
while (reader.pos < cEnd) {
|
|
801
|
+
const cTag = reader.uint32();
|
|
802
|
+
switch (cTag >>> 3) {
|
|
803
|
+
case 1:
|
|
804
|
+
info.participantSid = reader.string();
|
|
805
|
+
break;
|
|
806
|
+
case 2:
|
|
807
|
+
info.quality = reader.int32();
|
|
808
|
+
break;
|
|
809
|
+
case 3:
|
|
810
|
+
info.score = reader.float();
|
|
811
|
+
break;
|
|
812
|
+
default:
|
|
813
|
+
reader.skipType(cTag & 7);
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
message.updates.push(info);
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
default:
|
|
821
|
+
reader.skipType(tag & 7);
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return message;
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
var StreamStateUpdate = {
|
|
829
|
+
decode(input, length) {
|
|
830
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
831
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
832
|
+
const message = { streamStates: [] };
|
|
833
|
+
while (reader.pos < end) {
|
|
834
|
+
const tag = reader.uint32();
|
|
835
|
+
switch (tag >>> 3) {
|
|
836
|
+
case 1: {
|
|
837
|
+
const sEnd = reader.pos + reader.uint32();
|
|
838
|
+
const info = { participantSid: "", trackSid: "", state: 0 };
|
|
839
|
+
while (reader.pos < sEnd) {
|
|
840
|
+
const sTag = reader.uint32();
|
|
841
|
+
switch (sTag >>> 3) {
|
|
842
|
+
case 1:
|
|
843
|
+
info.participantSid = reader.string();
|
|
844
|
+
break;
|
|
845
|
+
case 2:
|
|
846
|
+
info.trackSid = reader.string();
|
|
847
|
+
break;
|
|
848
|
+
case 3:
|
|
849
|
+
info.state = reader.int32();
|
|
850
|
+
break;
|
|
851
|
+
default:
|
|
852
|
+
reader.skipType(sTag & 7);
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
message.streamStates.push(info);
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
default:
|
|
860
|
+
reader.skipType(tag & 7);
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return message;
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
var SignalRequest = {
|
|
868
|
+
encode(message, writer = _m0.Writer.create()) {
|
|
869
|
+
if (message.offer !== void 0) {
|
|
870
|
+
writeMessage(writer, 1, (w) => SessionDescription.encode(message.offer, w));
|
|
871
|
+
}
|
|
872
|
+
if (message.answer !== void 0) {
|
|
873
|
+
writeMessage(writer, 2, (w) => SessionDescription.encode(message.answer, w));
|
|
874
|
+
}
|
|
875
|
+
if (message.trickle !== void 0) {
|
|
876
|
+
writeMessage(writer, 3, (w) => TrickleRequest.encode(message.trickle, w));
|
|
877
|
+
}
|
|
878
|
+
if (message.addTrack !== void 0) {
|
|
879
|
+
writeMessage(writer, 4, (w) => AddTrackRequest.encode(message.addTrack, w));
|
|
880
|
+
}
|
|
881
|
+
if (message.mute !== void 0) {
|
|
882
|
+
writeMessage(writer, 5, (w) => MuteTrackRequest.encode(message.mute, w));
|
|
883
|
+
}
|
|
884
|
+
if (message.subscription !== void 0) {
|
|
885
|
+
writeMessage(writer, 6, (w) => UpdateSubscription.encode(message.subscription, w));
|
|
886
|
+
}
|
|
887
|
+
if (message.leave !== void 0) {
|
|
888
|
+
writeMessage(writer, 8, (w) => LeaveRequest.encode(message.leave, w));
|
|
889
|
+
}
|
|
890
|
+
if (message.ping !== void 0) {
|
|
891
|
+
writer.uint32(72).int64(message.ping);
|
|
892
|
+
}
|
|
893
|
+
return writer;
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
var SignalResponse = {
|
|
897
|
+
decode(input, length) {
|
|
898
|
+
const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
|
899
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
900
|
+
const message = {};
|
|
901
|
+
while (reader.pos < end) {
|
|
902
|
+
const tag = reader.uint32();
|
|
903
|
+
switch (tag >>> 3) {
|
|
904
|
+
case 1:
|
|
905
|
+
message.join = JoinResponse.decode(reader, reader.uint32());
|
|
906
|
+
break;
|
|
907
|
+
case 2:
|
|
908
|
+
message.answer = SessionDescription.decode(reader, reader.uint32());
|
|
909
|
+
break;
|
|
910
|
+
case 3:
|
|
911
|
+
message.offer = SessionDescription.decode(reader, reader.uint32());
|
|
912
|
+
break;
|
|
913
|
+
case 4:
|
|
914
|
+
message.trickle = TrickleRequest.decode(reader, reader.uint32());
|
|
915
|
+
break;
|
|
916
|
+
case 5:
|
|
917
|
+
message.update = ParticipantUpdate.decode(reader, reader.uint32());
|
|
918
|
+
break;
|
|
919
|
+
case 6:
|
|
920
|
+
message.trackPublished = TrackPublishedResponse.decode(reader, reader.uint32());
|
|
921
|
+
break;
|
|
922
|
+
case 8:
|
|
923
|
+
message.leave = LeaveRequest.decode(reader, reader.uint32());
|
|
924
|
+
break;
|
|
925
|
+
case 9:
|
|
926
|
+
message.mute = MuteTrackRequest.decode(reader, reader.uint32());
|
|
927
|
+
break;
|
|
928
|
+
case 10:
|
|
929
|
+
message.speakersChanged = SpeakersChanged.decode(reader, reader.uint32());
|
|
930
|
+
break;
|
|
931
|
+
case 11:
|
|
932
|
+
message.roomUpdate = RoomUpdate.decode(reader, reader.uint32());
|
|
933
|
+
break;
|
|
934
|
+
case 12:
|
|
935
|
+
message.connectionQuality = ConnectionQualityUpdate.decode(reader, reader.uint32());
|
|
936
|
+
break;
|
|
937
|
+
case 13:
|
|
938
|
+
message.streamStateUpdate = StreamStateUpdate.decode(reader, reader.uint32());
|
|
939
|
+
break;
|
|
940
|
+
case 15:
|
|
941
|
+
message.refreshToken = reader.string();
|
|
942
|
+
break;
|
|
943
|
+
case 17:
|
|
944
|
+
message.trackUnpublished = TrackUnpublishedResponse.decode(reader, reader.uint32());
|
|
945
|
+
break;
|
|
946
|
+
case 18:
|
|
947
|
+
message.pong = reader.int64();
|
|
948
|
+
break;
|
|
949
|
+
default:
|
|
950
|
+
reader.skipType(tag & 7);
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return message;
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
// src/proto/models.ts
|
|
959
|
+
var _m02 = __toESM(require("protobufjs/minimal"));
|
|
960
|
+
var TrackType = /* @__PURE__ */ ((TrackType2) => {
|
|
961
|
+
TrackType2[TrackType2["AUDIO"] = 0] = "AUDIO";
|
|
962
|
+
TrackType2[TrackType2["VIDEO"] = 1] = "VIDEO";
|
|
963
|
+
TrackType2[TrackType2["DATA"] = 2] = "DATA";
|
|
964
|
+
return TrackType2;
|
|
965
|
+
})(TrackType || {});
|
|
966
|
+
var TrackSource = /* @__PURE__ */ ((TrackSource2) => {
|
|
967
|
+
TrackSource2[TrackSource2["UNKNOWN"] = 0] = "UNKNOWN";
|
|
968
|
+
TrackSource2[TrackSource2["CAMERA"] = 1] = "CAMERA";
|
|
969
|
+
TrackSource2[TrackSource2["MICROPHONE"] = 2] = "MICROPHONE";
|
|
970
|
+
TrackSource2[TrackSource2["SCREEN_SHARE"] = 3] = "SCREEN_SHARE";
|
|
971
|
+
TrackSource2[TrackSource2["SCREEN_SHARE_AUDIO"] = 4] = "SCREEN_SHARE_AUDIO";
|
|
972
|
+
return TrackSource2;
|
|
973
|
+
})(TrackSource || {});
|
|
974
|
+
var ParticipantInfo_State = /* @__PURE__ */ ((ParticipantInfo_State2) => {
|
|
975
|
+
ParticipantInfo_State2[ParticipantInfo_State2["JOINING"] = 0] = "JOINING";
|
|
976
|
+
ParticipantInfo_State2[ParticipantInfo_State2["JOINED"] = 1] = "JOINED";
|
|
977
|
+
ParticipantInfo_State2[ParticipantInfo_State2["ACTIVE"] = 2] = "ACTIVE";
|
|
978
|
+
ParticipantInfo_State2[ParticipantInfo_State2["DISCONNECTED"] = 3] = "DISCONNECTED";
|
|
979
|
+
return ParticipantInfo_State2;
|
|
980
|
+
})(ParticipantInfo_State || {});
|
|
981
|
+
var DataPacket_Kind = /* @__PURE__ */ ((DataPacket_Kind2) => {
|
|
982
|
+
DataPacket_Kind2[DataPacket_Kind2["RELIABLE"] = 0] = "RELIABLE";
|
|
983
|
+
DataPacket_Kind2[DataPacket_Kind2["LOSSY"] = 1] = "LOSSY";
|
|
984
|
+
return DataPacket_Kind2;
|
|
985
|
+
})(DataPacket_Kind || {});
|
|
986
|
+
var ConnectionQuality = /* @__PURE__ */ ((ConnectionQuality2) => {
|
|
987
|
+
ConnectionQuality2[ConnectionQuality2["POOR"] = 0] = "POOR";
|
|
988
|
+
ConnectionQuality2[ConnectionQuality2["GOOD"] = 1] = "GOOD";
|
|
989
|
+
ConnectionQuality2[ConnectionQuality2["EXCELLENT"] = 2] = "EXCELLENT";
|
|
990
|
+
return ConnectionQuality2;
|
|
991
|
+
})(ConnectionQuality || {});
|
|
992
|
+
var DisconnectReason = /* @__PURE__ */ ((DisconnectReason2) => {
|
|
993
|
+
DisconnectReason2[DisconnectReason2["UNKNOWN_REASON"] = 0] = "UNKNOWN_REASON";
|
|
994
|
+
DisconnectReason2[DisconnectReason2["CLIENT_INITIATED"] = 1] = "CLIENT_INITIATED";
|
|
995
|
+
DisconnectReason2[DisconnectReason2["DUPLICATE_IDENTITY"] = 2] = "DUPLICATE_IDENTITY";
|
|
996
|
+
DisconnectReason2[DisconnectReason2["SERVER_SHUTDOWN"] = 3] = "SERVER_SHUTDOWN";
|
|
997
|
+
DisconnectReason2[DisconnectReason2["PARTICIPANT_REMOVED"] = 4] = "PARTICIPANT_REMOVED";
|
|
998
|
+
DisconnectReason2[DisconnectReason2["ROOM_DELETED"] = 5] = "ROOM_DELETED";
|
|
999
|
+
DisconnectReason2[DisconnectReason2["STATE_MISMATCH"] = 6] = "STATE_MISMATCH";
|
|
1000
|
+
DisconnectReason2[DisconnectReason2["JOIN_FAILURE"] = 7] = "JOIN_FAILURE";
|
|
1001
|
+
return DisconnectReason2;
|
|
1002
|
+
})(DisconnectReason || {});
|
|
1003
|
+
var DataPacket = {
|
|
1004
|
+
encode(message, writer = _m02.Writer.create()) {
|
|
1005
|
+
if (message.kind !== 0) {
|
|
1006
|
+
writer.uint32(8).int32(message.kind);
|
|
1007
|
+
}
|
|
1008
|
+
if (message.user !== void 0) {
|
|
1009
|
+
UserPacket.encode(message.user, writer.uint32(18).fork()).ldelim();
|
|
1010
|
+
}
|
|
1011
|
+
if (message.speaker !== void 0) {
|
|
1012
|
+
ActiveSpeakerUpdate.encode(message.speaker, writer.uint32(26).fork()).ldelim();
|
|
1013
|
+
}
|
|
1014
|
+
return writer;
|
|
1015
|
+
},
|
|
1016
|
+
decode(input, length) {
|
|
1017
|
+
const reader = input instanceof _m02.Reader ? input : _m02.Reader.create(input);
|
|
1018
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
1019
|
+
const message = { kind: 0 };
|
|
1020
|
+
while (reader.pos < end) {
|
|
1021
|
+
const tag = reader.uint32();
|
|
1022
|
+
switch (tag >>> 3) {
|
|
1023
|
+
case 1:
|
|
1024
|
+
message.kind = reader.int32();
|
|
1025
|
+
break;
|
|
1026
|
+
case 2:
|
|
1027
|
+
message.user = UserPacket.decode(reader, reader.uint32());
|
|
1028
|
+
break;
|
|
1029
|
+
case 3:
|
|
1030
|
+
message.speaker = ActiveSpeakerUpdate.decode(reader, reader.uint32());
|
|
1031
|
+
break;
|
|
1032
|
+
default:
|
|
1033
|
+
reader.skipType(tag & 7);
|
|
1034
|
+
break;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return message;
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
var UserPacket = {
|
|
1041
|
+
encode(message, writer = _m02.Writer.create()) {
|
|
1042
|
+
if (message.participantSid !== "") {
|
|
1043
|
+
writer.uint32(10).string(message.participantSid);
|
|
1044
|
+
}
|
|
1045
|
+
if (message.payload.length !== 0) {
|
|
1046
|
+
writer.uint32(18).bytes(message.payload);
|
|
1047
|
+
}
|
|
1048
|
+
for (const v of message.destinationSids) {
|
|
1049
|
+
writer.uint32(26).string(v);
|
|
1050
|
+
}
|
|
1051
|
+
if (message.topic !== void 0) {
|
|
1052
|
+
writer.uint32(34).string(message.topic);
|
|
1053
|
+
}
|
|
1054
|
+
return writer;
|
|
1055
|
+
},
|
|
1056
|
+
decode(input, length) {
|
|
1057
|
+
const reader = input instanceof _m02.Reader ? input : _m02.Reader.create(input);
|
|
1058
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
1059
|
+
const message = {
|
|
1060
|
+
participantSid: "",
|
|
1061
|
+
payload: new Uint8Array(),
|
|
1062
|
+
destinationSids: []
|
|
1063
|
+
};
|
|
1064
|
+
while (reader.pos < end) {
|
|
1065
|
+
const tag = reader.uint32();
|
|
1066
|
+
switch (tag >>> 3) {
|
|
1067
|
+
case 1:
|
|
1068
|
+
message.participantSid = reader.string();
|
|
1069
|
+
break;
|
|
1070
|
+
case 2:
|
|
1071
|
+
message.payload = reader.bytes();
|
|
1072
|
+
break;
|
|
1073
|
+
case 3:
|
|
1074
|
+
message.destinationSids.push(reader.string());
|
|
1075
|
+
break;
|
|
1076
|
+
case 4:
|
|
1077
|
+
message.topic = reader.string();
|
|
1078
|
+
break;
|
|
1079
|
+
default:
|
|
1080
|
+
reader.skipType(tag & 7);
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return message;
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
var ActiveSpeakerUpdate = {
|
|
1088
|
+
encode(message, writer = _m02.Writer.create()) {
|
|
1089
|
+
for (const v of message.speakers) {
|
|
1090
|
+
SpeakerInfo.encode(v, writer.uint32(10).fork()).ldelim();
|
|
1091
|
+
}
|
|
1092
|
+
return writer;
|
|
1093
|
+
},
|
|
1094
|
+
decode(input, length) {
|
|
1095
|
+
const reader = input instanceof _m02.Reader ? input : _m02.Reader.create(input);
|
|
1096
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
1097
|
+
const message = { speakers: [] };
|
|
1098
|
+
while (reader.pos < end) {
|
|
1099
|
+
const tag = reader.uint32();
|
|
1100
|
+
switch (tag >>> 3) {
|
|
1101
|
+
case 1:
|
|
1102
|
+
message.speakers.push(SpeakerInfo.decode(reader, reader.uint32()));
|
|
1103
|
+
break;
|
|
1104
|
+
default:
|
|
1105
|
+
reader.skipType(tag & 7);
|
|
1106
|
+
break;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return message;
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
var SpeakerInfo = {
|
|
1113
|
+
encode(message, writer = _m02.Writer.create()) {
|
|
1114
|
+
if (message.sid !== "") {
|
|
1115
|
+
writer.uint32(10).string(message.sid);
|
|
1116
|
+
}
|
|
1117
|
+
if (message.level !== 0) {
|
|
1118
|
+
writer.uint32(21).float(message.level);
|
|
1119
|
+
}
|
|
1120
|
+
if (message.active) {
|
|
1121
|
+
writer.uint32(24).bool(message.active);
|
|
1122
|
+
}
|
|
1123
|
+
return writer;
|
|
1124
|
+
},
|
|
1125
|
+
decode(input, length) {
|
|
1126
|
+
const reader = input instanceof _m02.Reader ? input : _m02.Reader.create(input);
|
|
1127
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
1128
|
+
const message = { sid: "", level: 0, active: false };
|
|
1129
|
+
while (reader.pos < end) {
|
|
1130
|
+
const tag = reader.uint32();
|
|
1131
|
+
switch (tag >>> 3) {
|
|
1132
|
+
case 1:
|
|
1133
|
+
message.sid = reader.string();
|
|
1134
|
+
break;
|
|
1135
|
+
case 2:
|
|
1136
|
+
message.level = reader.float();
|
|
1137
|
+
break;
|
|
1138
|
+
case 3:
|
|
1139
|
+
message.active = reader.bool();
|
|
1140
|
+
break;
|
|
1141
|
+
default:
|
|
1142
|
+
reader.skipType(tag & 7);
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return message;
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
// src/signal.ts
|
|
1151
|
+
var log = createLogger("SignalClient");
|
|
1152
|
+
var PROTOCOL_VERSION = 8;
|
|
1153
|
+
var SDK_NAME = "node";
|
|
1154
|
+
var SDK_VERSION = "0.1.0";
|
|
1155
|
+
var SignalClient = class extends TypedEmitter {
|
|
1156
|
+
ws = null;
|
|
1157
|
+
pingInterval = null;
|
|
1158
|
+
_isConnected = false;
|
|
1159
|
+
joinResponse = null;
|
|
1160
|
+
get isConnected() {
|
|
1161
|
+
return this._isConnected;
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Connect to the dTelecom SFU signaling server.
|
|
1165
|
+
* Returns JoinResponse on successful connection.
|
|
1166
|
+
*/
|
|
1167
|
+
async connect(url, token, options = {}) {
|
|
1168
|
+
const autoSubscribe = options.autoSubscribe ?? true;
|
|
1169
|
+
const connectTimeout = options.connectTimeout ?? 1e4;
|
|
1170
|
+
const wsUrl = this.buildUrl(url, token, autoSubscribe);
|
|
1171
|
+
log.info(`Connecting to ${wsUrl.replace(/access_token=[^&]+/, "access_token=***")}`);
|
|
1172
|
+
return new Promise((resolve, reject) => {
|
|
1173
|
+
const timeout = setTimeout(() => {
|
|
1174
|
+
this.close();
|
|
1175
|
+
reject(new Error(`Signal connection timed out after ${connectTimeout}ms`));
|
|
1176
|
+
}, connectTimeout);
|
|
1177
|
+
try {
|
|
1178
|
+
this.ws = new import_ws.default(wsUrl, {
|
|
1179
|
+
headers: {
|
|
1180
|
+
Authorization: `Bearer ${token}`
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
this.ws.binaryType = "arraybuffer";
|
|
1184
|
+
} catch (err) {
|
|
1185
|
+
clearTimeout(timeout);
|
|
1186
|
+
reject(err);
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
this.ws.on("open", () => {
|
|
1190
|
+
log.debug("WebSocket connected");
|
|
1191
|
+
});
|
|
1192
|
+
this.ws.on("message", (data) => {
|
|
1193
|
+
try {
|
|
1194
|
+
const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : new Uint8Array(data);
|
|
1195
|
+
const response = SignalResponse.decode(bytes);
|
|
1196
|
+
this.handleResponse(response);
|
|
1197
|
+
if (response.join) {
|
|
1198
|
+
clearTimeout(timeout);
|
|
1199
|
+
this.joinResponse = response.join;
|
|
1200
|
+
this._isConnected = true;
|
|
1201
|
+
if (response.join.pingInterval > 0) {
|
|
1202
|
+
this.startPing(response.join.pingInterval);
|
|
1203
|
+
}
|
|
1204
|
+
resolve(response.join);
|
|
1205
|
+
}
|
|
1206
|
+
} catch (err) {
|
|
1207
|
+
log.error("Failed to decode signal response", err);
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
this.ws.on("error", (err) => {
|
|
1211
|
+
log.error("WebSocket error", err);
|
|
1212
|
+
clearTimeout(timeout);
|
|
1213
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
1214
|
+
reject(err);
|
|
1215
|
+
});
|
|
1216
|
+
this.ws.on("close", (code, reason) => {
|
|
1217
|
+
clearTimeout(timeout);
|
|
1218
|
+
this._isConnected = false;
|
|
1219
|
+
this.stopPing();
|
|
1220
|
+
const reasonStr = reason?.toString() || `code ${code}`;
|
|
1221
|
+
log.info(`WebSocket closed: ${reasonStr}`);
|
|
1222
|
+
this.emit("close", reasonStr);
|
|
1223
|
+
if (!this.joinResponse) {
|
|
1224
|
+
reject(new Error(`WebSocket closed before join: ${reasonStr}`));
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
/** Send an SDP offer (publisher → server) */
|
|
1230
|
+
sendOffer(sd) {
|
|
1231
|
+
this.sendRequest({ offer: sd });
|
|
1232
|
+
}
|
|
1233
|
+
/** Send an SDP answer (subscriber → server) */
|
|
1234
|
+
sendAnswer(sd) {
|
|
1235
|
+
this.sendRequest({ answer: sd });
|
|
1236
|
+
}
|
|
1237
|
+
/** Send an ICE candidate */
|
|
1238
|
+
sendIceCandidate(candidate, target) {
|
|
1239
|
+
this.sendRequest({
|
|
1240
|
+
trickle: { candidateInit: candidate, target }
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
/** Request to add (publish) a track */
|
|
1244
|
+
sendAddTrack(request) {
|
|
1245
|
+
this.sendRequest({ addTrack: request });
|
|
1246
|
+
}
|
|
1247
|
+
/** Mute/unmute a track */
|
|
1248
|
+
sendMuteTrack(trackSid, muted) {
|
|
1249
|
+
this.sendRequest({ mute: { sid: trackSid, muted } });
|
|
1250
|
+
}
|
|
1251
|
+
/** Update track subscription */
|
|
1252
|
+
sendSubscription(update) {
|
|
1253
|
+
this.sendRequest({ subscription: update });
|
|
1254
|
+
}
|
|
1255
|
+
/** Send leave request */
|
|
1256
|
+
sendLeave() {
|
|
1257
|
+
this.sendRequest({
|
|
1258
|
+
leave: { canReconnect: false, reason: 1 /* CLIENT_INITIATED */ }
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
/** Close the WebSocket connection */
|
|
1262
|
+
close() {
|
|
1263
|
+
this.stopPing();
|
|
1264
|
+
this._isConnected = false;
|
|
1265
|
+
if (this.ws) {
|
|
1266
|
+
this.ws.removeAllListeners();
|
|
1267
|
+
if (this.ws.readyState === import_ws.default.OPEN || this.ws.readyState === import_ws.default.CONNECTING) {
|
|
1268
|
+
this.ws.close();
|
|
1269
|
+
}
|
|
1270
|
+
this.ws = null;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
// ─── Private ────────────────────────────────────────────────────────────
|
|
1274
|
+
buildUrl(url, token, autoSubscribe) {
|
|
1275
|
+
let wsUrl = url;
|
|
1276
|
+
if (wsUrl.startsWith("http://")) {
|
|
1277
|
+
wsUrl = wsUrl.replace("http://", "ws://");
|
|
1278
|
+
} else if (wsUrl.startsWith("https://")) {
|
|
1279
|
+
wsUrl = wsUrl.replace("https://", "wss://");
|
|
1280
|
+
} else if (!wsUrl.startsWith("ws://") && !wsUrl.startsWith("wss://")) {
|
|
1281
|
+
wsUrl = `wss://${wsUrl}`;
|
|
1282
|
+
}
|
|
1283
|
+
if (!wsUrl.includes("/rtc")) {
|
|
1284
|
+
wsUrl = wsUrl.replace(/\/?$/, "/rtc");
|
|
1285
|
+
}
|
|
1286
|
+
const params = new URLSearchParams({
|
|
1287
|
+
protocol: String(PROTOCOL_VERSION),
|
|
1288
|
+
sdk: SDK_NAME,
|
|
1289
|
+
version: SDK_VERSION,
|
|
1290
|
+
auto_subscribe: autoSubscribe ? "1" : "0",
|
|
1291
|
+
access_token: token
|
|
1292
|
+
});
|
|
1293
|
+
return `${wsUrl}?${params.toString()}`;
|
|
1294
|
+
}
|
|
1295
|
+
sendRequest(request) {
|
|
1296
|
+
if (!this.ws || this.ws.readyState !== import_ws.default.OPEN) {
|
|
1297
|
+
log.warn("Cannot send: WebSocket not open");
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
try {
|
|
1301
|
+
const bytes = SignalRequest.encode(request).finish();
|
|
1302
|
+
this.ws.send(bytes);
|
|
1303
|
+
} catch (err) {
|
|
1304
|
+
log.error("Failed to encode signal request", err);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
handleResponse(response) {
|
|
1308
|
+
if (response.offer) {
|
|
1309
|
+
log.debug(`Received OFFER from server (type=${response.offer.type})`);
|
|
1310
|
+
this.emit("offer", response.offer);
|
|
1311
|
+
}
|
|
1312
|
+
if (response.answer) {
|
|
1313
|
+
log.debug(`Received ANSWER from server (type=${response.answer.type})`);
|
|
1314
|
+
this.emit("answer", response.answer);
|
|
1315
|
+
}
|
|
1316
|
+
if (response.trickle) {
|
|
1317
|
+
this.emit("trickle", response.trickle);
|
|
1318
|
+
}
|
|
1319
|
+
if (response.update) {
|
|
1320
|
+
this.emit("participantUpdate", response.update);
|
|
1321
|
+
}
|
|
1322
|
+
if (response.trackPublished) {
|
|
1323
|
+
log.debug("Track published confirmed:", response.trackPublished.cid);
|
|
1324
|
+
this.emit("trackPublished", response.trackPublished);
|
|
1325
|
+
}
|
|
1326
|
+
if (response.trackUnpublished) {
|
|
1327
|
+
this.emit("trackUnpublished", response.trackUnpublished);
|
|
1328
|
+
}
|
|
1329
|
+
if (response.speakersChanged) {
|
|
1330
|
+
this.emit("speakersChanged", response.speakersChanged);
|
|
1331
|
+
}
|
|
1332
|
+
if (response.roomUpdate) {
|
|
1333
|
+
this.emit("roomUpdate", response.roomUpdate);
|
|
1334
|
+
}
|
|
1335
|
+
if (response.connectionQuality) {
|
|
1336
|
+
this.emit("connectionQuality", response.connectionQuality);
|
|
1337
|
+
}
|
|
1338
|
+
if (response.streamStateUpdate) {
|
|
1339
|
+
this.emit("streamStateUpdate", response.streamStateUpdate);
|
|
1340
|
+
}
|
|
1341
|
+
if (response.leave) {
|
|
1342
|
+
log.info("Server requested leave", response.leave.reason);
|
|
1343
|
+
this.emit("leave", response.leave);
|
|
1344
|
+
}
|
|
1345
|
+
if (response.refreshToken) {
|
|
1346
|
+
this.emit("tokenRefresh", response.refreshToken);
|
|
1347
|
+
}
|
|
1348
|
+
if (response.mute) {
|
|
1349
|
+
}
|
|
1350
|
+
if (response.pong !== void 0) {
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
startPing(intervalSec) {
|
|
1354
|
+
this.stopPing();
|
|
1355
|
+
const intervalMs = intervalSec * 1e3;
|
|
1356
|
+
this.pingInterval = setInterval(() => {
|
|
1357
|
+
this.sendRequest({ ping: Date.now() });
|
|
1358
|
+
}, intervalMs);
|
|
1359
|
+
}
|
|
1360
|
+
stopPing() {
|
|
1361
|
+
if (this.pingInterval) {
|
|
1362
|
+
clearInterval(this.pingInterval);
|
|
1363
|
+
this.pingInterval = null;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
|
|
1368
|
+
// src/engine.ts
|
|
1369
|
+
var log2 = createLogger("RTCEngine");
|
|
1370
|
+
var RTCEngine = class extends TypedEmitter {
|
|
1371
|
+
signal;
|
|
1372
|
+
publisher = null;
|
|
1373
|
+
subscriber = null;
|
|
1374
|
+
reliableChannel = null;
|
|
1375
|
+
lossyChannel = null;
|
|
1376
|
+
subscriberReliableChannel = null;
|
|
1377
|
+
subscriberLossyChannel = null;
|
|
1378
|
+
subscriberPrimary = true;
|
|
1379
|
+
_isConnected = false;
|
|
1380
|
+
pendingCandidates = [];
|
|
1381
|
+
joinResponse = null;
|
|
1382
|
+
publishWaiters = /* @__PURE__ */ new Map();
|
|
1383
|
+
get isConnected() {
|
|
1384
|
+
return this._isConnected;
|
|
1385
|
+
}
|
|
1386
|
+
get publisherPC() {
|
|
1387
|
+
return this.publisher;
|
|
1388
|
+
}
|
|
1389
|
+
get subscriberPC() {
|
|
1390
|
+
return this.subscriber;
|
|
1391
|
+
}
|
|
1392
|
+
get reliableDataChannel() {
|
|
1393
|
+
return this.reliableChannel;
|
|
1394
|
+
}
|
|
1395
|
+
get lossyDataChannel() {
|
|
1396
|
+
return this.lossyChannel;
|
|
1397
|
+
}
|
|
1398
|
+
constructor() {
|
|
1399
|
+
super();
|
|
1400
|
+
this.signal = new SignalClient();
|
|
1401
|
+
}
|
|
1402
|
+
async connect(url, token, options = {}) {
|
|
1403
|
+
const joinResponse = await this.signal.connect(url, token, {
|
|
1404
|
+
autoSubscribe: options.autoSubscribe ?? true,
|
|
1405
|
+
connectTimeout: options.connectTimeout ?? 1e4
|
|
1406
|
+
});
|
|
1407
|
+
this.joinResponse = joinResponse;
|
|
1408
|
+
this.subscriberPrimary = joinResponse.subscriberPrimary;
|
|
1409
|
+
log2.info(`Joined room "${joinResponse.room?.name}", subscriber_primary=${this.subscriberPrimary}`);
|
|
1410
|
+
log2.debug(`ICE servers: ${joinResponse.iceServers.length}, participants: ${joinResponse.otherParticipants.length}`);
|
|
1411
|
+
const iceServers = this.buildIceServers(joinResponse.iceServers);
|
|
1412
|
+
this.createPublisher(iceServers);
|
|
1413
|
+
this.createSubscriber(iceServers);
|
|
1414
|
+
this.setupSignalHandlers();
|
|
1415
|
+
this.createDataChannels();
|
|
1416
|
+
if (!this.subscriberPrimary) {
|
|
1417
|
+
await this.negotiate();
|
|
1418
|
+
}
|
|
1419
|
+
return joinResponse;
|
|
1420
|
+
}
|
|
1421
|
+
async addTransceiver(track) {
|
|
1422
|
+
if (!this.publisher) {
|
|
1423
|
+
throw new Error("Publisher PC not initialized");
|
|
1424
|
+
}
|
|
1425
|
+
const transceiver = this.publisher.addTransceiver(track, { direction: "sendonly" });
|
|
1426
|
+
return transceiver;
|
|
1427
|
+
}
|
|
1428
|
+
async requestPublishTrack(cid, name, type, source, options) {
|
|
1429
|
+
const request = {
|
|
1430
|
+
cid,
|
|
1431
|
+
name,
|
|
1432
|
+
type,
|
|
1433
|
+
source,
|
|
1434
|
+
width: 0,
|
|
1435
|
+
height: 0,
|
|
1436
|
+
muted: options?.muted ?? false,
|
|
1437
|
+
disableDtx: options?.disableDtx ?? false,
|
|
1438
|
+
layers: [],
|
|
1439
|
+
sid: ""
|
|
1440
|
+
};
|
|
1441
|
+
return new Promise((resolve) => {
|
|
1442
|
+
this.publishWaiters.set(cid, resolve);
|
|
1443
|
+
this.signal.sendAddTrack(request);
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
async negotiate() {
|
|
1447
|
+
if (!this.publisher) {
|
|
1448
|
+
throw new Error("Publisher PC not initialized");
|
|
1449
|
+
}
|
|
1450
|
+
log2.debug(`Publisher signaling state before negotiate: ${this.publisher.signalingState}`);
|
|
1451
|
+
const offer = await this.publisher.createOffer();
|
|
1452
|
+
await this.publisher.setLocalDescription(offer);
|
|
1453
|
+
log2.debug("Sending publisher offer");
|
|
1454
|
+
this.signal.sendOffer({
|
|
1455
|
+
type: offer.type,
|
|
1456
|
+
sdp: offer.sdp
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
sendData(data, kind) {
|
|
1460
|
+
const channel = kind === "reliable" ? this.reliableChannel : this.lossyChannel;
|
|
1461
|
+
if (!channel || channel.readyState !== "open") {
|
|
1462
|
+
log2.warn(`Data channel ${kind} not open`);
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
channel.send(Buffer.from(data));
|
|
1466
|
+
}
|
|
1467
|
+
async disconnect() {
|
|
1468
|
+
this._isConnected = false;
|
|
1469
|
+
try {
|
|
1470
|
+
this.signal.sendLeave();
|
|
1471
|
+
} catch {
|
|
1472
|
+
}
|
|
1473
|
+
this.reliableChannel?.close();
|
|
1474
|
+
this.lossyChannel?.close();
|
|
1475
|
+
this.publisher?.close();
|
|
1476
|
+
this.subscriber?.close();
|
|
1477
|
+
this.signal.close();
|
|
1478
|
+
this.publisher = null;
|
|
1479
|
+
this.subscriber = null;
|
|
1480
|
+
this.reliableChannel = null;
|
|
1481
|
+
this.lossyChannel = null;
|
|
1482
|
+
this.subscriberReliableChannel = null;
|
|
1483
|
+
this.subscriberLossyChannel = null;
|
|
1484
|
+
this.publishWaiters.clear();
|
|
1485
|
+
log2.info("Disconnected");
|
|
1486
|
+
this.emit("disconnected", "client_initiated");
|
|
1487
|
+
}
|
|
1488
|
+
// ─── Private ────────────────────────────────────────────────────────────
|
|
1489
|
+
buildIceServers(servers) {
|
|
1490
|
+
const result = [];
|
|
1491
|
+
for (const s of servers) {
|
|
1492
|
+
for (const url of s.urls) {
|
|
1493
|
+
result.push({
|
|
1494
|
+
urls: url,
|
|
1495
|
+
username: s.username || void 0,
|
|
1496
|
+
credential: s.credential || void 0
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return result;
|
|
1501
|
+
}
|
|
1502
|
+
createPublisher(iceServers) {
|
|
1503
|
+
this.publisher = new import_werift.RTCPeerConnection({
|
|
1504
|
+
iceServers,
|
|
1505
|
+
iceTransportPolicy: "all"
|
|
1506
|
+
});
|
|
1507
|
+
this.publisher.onIceCandidate.subscribe((candidate) => {
|
|
1508
|
+
if (candidate) {
|
|
1509
|
+
const init = JSON.stringify(candidate.toJSON());
|
|
1510
|
+
this.signal.sendIceCandidate(init, 0 /* PUBLISHER */);
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1513
|
+
this.publisher.iceConnectionStateChange.subscribe((state) => {
|
|
1514
|
+
log2.debug(`Publisher ICE state: ${state}`);
|
|
1515
|
+
});
|
|
1516
|
+
log2.debug("Publisher PC created");
|
|
1517
|
+
}
|
|
1518
|
+
createSubscriber(iceServers) {
|
|
1519
|
+
this.subscriber = new import_werift.RTCPeerConnection({
|
|
1520
|
+
iceServers,
|
|
1521
|
+
iceTransportPolicy: "all"
|
|
1522
|
+
});
|
|
1523
|
+
this.subscriber.onIceCandidate.subscribe((candidate) => {
|
|
1524
|
+
if (candidate) {
|
|
1525
|
+
const init = JSON.stringify(candidate.toJSON());
|
|
1526
|
+
this.signal.sendIceCandidate(init, 1 /* SUBSCRIBER */);
|
|
1527
|
+
}
|
|
1528
|
+
});
|
|
1529
|
+
this.subscriber.on("track", (event) => {
|
|
1530
|
+
const track = event.track;
|
|
1531
|
+
const transceiver = event.transceiver;
|
|
1532
|
+
log2.debug(`Subscriber received track: mid=${transceiver?.mid}, kind=${track?.kind}`);
|
|
1533
|
+
if (track) {
|
|
1534
|
+
this.emit("remoteTrack", track, transceiver);
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
this.subscriber.onDataChannel.subscribe((channel) => {
|
|
1538
|
+
log2.debug(`Subscriber data channel: "${channel.label}"`);
|
|
1539
|
+
if (channel.label === "_reliable") {
|
|
1540
|
+
this.subscriberReliableChannel = channel;
|
|
1541
|
+
this.setupSubscriberDataChannel(channel, "reliable");
|
|
1542
|
+
} else if (channel.label === "_lossy") {
|
|
1543
|
+
this.subscriberLossyChannel = channel;
|
|
1544
|
+
this.setupSubscriberDataChannel(channel, "lossy");
|
|
1545
|
+
}
|
|
1546
|
+
});
|
|
1547
|
+
this.subscriber.iceConnectionStateChange.subscribe((state) => {
|
|
1548
|
+
log2.debug(`Subscriber ICE state: ${state}`);
|
|
1549
|
+
if (state === "connected") {
|
|
1550
|
+
if (!this._isConnected) {
|
|
1551
|
+
this._isConnected = true;
|
|
1552
|
+
this.emit("connected");
|
|
1553
|
+
}
|
|
1554
|
+
} else if (state === "disconnected" || state === "failed") {
|
|
1555
|
+
this._isConnected = false;
|
|
1556
|
+
this.emit("disconnected", `ICE ${state}`);
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
log2.debug("Subscriber PC created");
|
|
1560
|
+
}
|
|
1561
|
+
createDataChannels() {
|
|
1562
|
+
if (!this.publisher) return;
|
|
1563
|
+
this.reliableChannel = this.publisher.createDataChannel("_reliable", {
|
|
1564
|
+
ordered: true
|
|
1565
|
+
});
|
|
1566
|
+
this.lossyChannel = this.publisher.createDataChannel("_lossy", {
|
|
1567
|
+
ordered: true,
|
|
1568
|
+
maxRetransmits: 1
|
|
1569
|
+
});
|
|
1570
|
+
let readyCount = 0;
|
|
1571
|
+
const checkReady = () => {
|
|
1572
|
+
readyCount++;
|
|
1573
|
+
if (readyCount >= 2) {
|
|
1574
|
+
log2.debug("Publisher data channels ready");
|
|
1575
|
+
this.emit("dataChannelReady");
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
this.reliableChannel.stateChanged.subscribe((state) => {
|
|
1579
|
+
if (state === "open") {
|
|
1580
|
+
log2.debug("Reliable data channel opened");
|
|
1581
|
+
checkReady();
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
1584
|
+
this.lossyChannel.stateChanged.subscribe((state) => {
|
|
1585
|
+
if (state === "open") {
|
|
1586
|
+
log2.debug("Lossy data channel opened");
|
|
1587
|
+
checkReady();
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
log2.debug("Data channels created on publisher");
|
|
1591
|
+
}
|
|
1592
|
+
setupSubscriberDataChannel(channel, kind) {
|
|
1593
|
+
channel.onMessage.subscribe((event) => {
|
|
1594
|
+
let data;
|
|
1595
|
+
if (event instanceof Buffer) {
|
|
1596
|
+
data = new Uint8Array(event);
|
|
1597
|
+
} else if (typeof event === "string") {
|
|
1598
|
+
data = new TextEncoder().encode(event);
|
|
1599
|
+
} else {
|
|
1600
|
+
data = new Uint8Array(event);
|
|
1601
|
+
}
|
|
1602
|
+
this.emit("dataMessage", data, kind);
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
setupSignalHandlers() {
|
|
1606
|
+
this.signal.on("offer", async (sd) => {
|
|
1607
|
+
if (!this.subscriber) return;
|
|
1608
|
+
try {
|
|
1609
|
+
await this.subscriber.setRemoteDescription(
|
|
1610
|
+
new import_werift.RTCSessionDescription(sd.sdp, sd.type)
|
|
1611
|
+
);
|
|
1612
|
+
const answer = await this.subscriber.createAnswer();
|
|
1613
|
+
await this.subscriber.setLocalDescription(answer);
|
|
1614
|
+
this.signal.sendAnswer({
|
|
1615
|
+
type: answer.type,
|
|
1616
|
+
sdp: answer.sdp
|
|
1617
|
+
});
|
|
1618
|
+
log2.debug("Sent subscriber answer");
|
|
1619
|
+
} catch (err) {
|
|
1620
|
+
log2.error("Failed to handle subscriber offer", err);
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
this.signal.on("answer", async (sd) => {
|
|
1624
|
+
if (!this.publisher) return;
|
|
1625
|
+
try {
|
|
1626
|
+
await this.publisher.setRemoteDescription(
|
|
1627
|
+
new import_werift.RTCSessionDescription(sd.sdp, sd.type)
|
|
1628
|
+
);
|
|
1629
|
+
log2.debug("Set publisher remote description");
|
|
1630
|
+
this.flushPendingCandidates(0 /* PUBLISHER */);
|
|
1631
|
+
} catch (err) {
|
|
1632
|
+
log2.error("Failed to handle publisher answer", err);
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
this.signal.on("trickle", async (trickle) => {
|
|
1636
|
+
try {
|
|
1637
|
+
const candidateInit = JSON.parse(trickle.candidateInit);
|
|
1638
|
+
const candidate = new import_werift.RTCIceCandidate(candidateInit);
|
|
1639
|
+
const pc = trickle.target === 0 /* PUBLISHER */ ? this.publisher : this.subscriber;
|
|
1640
|
+
if (!pc) return;
|
|
1641
|
+
if (pc.remoteDescription) {
|
|
1642
|
+
await pc.addIceCandidate(candidate);
|
|
1643
|
+
} else {
|
|
1644
|
+
this.pendingCandidates.push({ candidate, target: trickle.target });
|
|
1645
|
+
}
|
|
1646
|
+
} catch (err) {
|
|
1647
|
+
log2.error("Failed to add ICE candidate", err);
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
this.signal.on("trackPublished", (response) => {
|
|
1651
|
+
const waiter = this.publishWaiters.get(response.cid);
|
|
1652
|
+
if (waiter) {
|
|
1653
|
+
this.publishWaiters.delete(response.cid);
|
|
1654
|
+
waiter(response);
|
|
1655
|
+
}
|
|
1656
|
+
this.emit("trackPublished", response);
|
|
1657
|
+
});
|
|
1658
|
+
this.signal.on("leave", () => {
|
|
1659
|
+
this.disconnect();
|
|
1660
|
+
});
|
|
1661
|
+
this.signal.on("close", (reason) => {
|
|
1662
|
+
if (this._isConnected) {
|
|
1663
|
+
this._isConnected = false;
|
|
1664
|
+
this.emit("disconnected", reason);
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
flushPendingCandidates(target) {
|
|
1669
|
+
const pc = target === 0 /* PUBLISHER */ ? this.publisher : this.subscriber;
|
|
1670
|
+
if (!pc) return;
|
|
1671
|
+
const toFlush = this.pendingCandidates.filter((c) => c.target === target);
|
|
1672
|
+
this.pendingCandidates = this.pendingCandidates.filter((c) => c.target !== target);
|
|
1673
|
+
for (const { candidate } of toFlush) {
|
|
1674
|
+
pc.addIceCandidate(candidate).catch((err) => {
|
|
1675
|
+
log2.error("Failed to flush ICE candidate", err);
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
// src/track.ts
|
|
1682
|
+
var import_werift2 = require("werift");
|
|
1683
|
+
|
|
1684
|
+
// src/audio/audio-frame.ts
|
|
1685
|
+
var AudioFrame = class _AudioFrame {
|
|
1686
|
+
/** PCM16 samples (interleaved if stereo) */
|
|
1687
|
+
data;
|
|
1688
|
+
/** Sample rate in Hz (e.g. 16000, 48000) */
|
|
1689
|
+
sampleRate;
|
|
1690
|
+
/** Number of channels (1 = mono, 2 = stereo) */
|
|
1691
|
+
channels;
|
|
1692
|
+
/** Number of samples per channel */
|
|
1693
|
+
samplesPerChannel;
|
|
1694
|
+
constructor(data, sampleRate, channels, samplesPerChannel) {
|
|
1695
|
+
this.data = data;
|
|
1696
|
+
this.sampleRate = sampleRate;
|
|
1697
|
+
this.channels = channels;
|
|
1698
|
+
this.samplesPerChannel = samplesPerChannel;
|
|
1699
|
+
}
|
|
1700
|
+
/** Create an empty (silent) AudioFrame */
|
|
1701
|
+
static create(sampleRate, channels, samplesPerChannel) {
|
|
1702
|
+
const data = new Int16Array(samplesPerChannel * channels);
|
|
1703
|
+
return new _AudioFrame(data, sampleRate, channels, samplesPerChannel);
|
|
1704
|
+
}
|
|
1705
|
+
/** Duration of this frame in seconds */
|
|
1706
|
+
get duration() {
|
|
1707
|
+
return this.samplesPerChannel / this.sampleRate;
|
|
1708
|
+
}
|
|
1709
|
+
/** Duration of this frame in milliseconds */
|
|
1710
|
+
get durationMs() {
|
|
1711
|
+
return this.samplesPerChannel / this.sampleRate * 1e3;
|
|
1712
|
+
}
|
|
1713
|
+
/** Total number of samples (channels * samplesPerChannel) */
|
|
1714
|
+
get totalSamples() {
|
|
1715
|
+
return this.data.length;
|
|
1716
|
+
}
|
|
1717
|
+
/** Convert to Buffer (for Opus encoder or file I/O) */
|
|
1718
|
+
toBuffer() {
|
|
1719
|
+
return Buffer.from(this.data.buffer, this.data.byteOffset, this.data.byteLength);
|
|
1720
|
+
}
|
|
1721
|
+
/** Create AudioFrame from a Buffer of PCM16 data */
|
|
1722
|
+
static fromBuffer(buffer, sampleRate, channels) {
|
|
1723
|
+
const data = new Int16Array(
|
|
1724
|
+
buffer.buffer,
|
|
1725
|
+
buffer.byteOffset,
|
|
1726
|
+
buffer.byteLength / 2
|
|
1727
|
+
);
|
|
1728
|
+
const samplesPerChannel = data.length / channels;
|
|
1729
|
+
return new _AudioFrame(data, sampleRate, channels, samplesPerChannel);
|
|
1730
|
+
}
|
|
1731
|
+
/** Clone this AudioFrame */
|
|
1732
|
+
clone() {
|
|
1733
|
+
return new _AudioFrame(
|
|
1734
|
+
new Int16Array(this.data),
|
|
1735
|
+
this.sampleRate,
|
|
1736
|
+
this.channels,
|
|
1737
|
+
this.samplesPerChannel
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
|
|
1742
|
+
// src/audio/opus-encoder.ts
|
|
1743
|
+
var log3 = createLogger("OpusEncoder");
|
|
1744
|
+
var OPUS_SAMPLE_RATE = 48e3;
|
|
1745
|
+
var OPUS_FRAME_DURATION_MS = 20;
|
|
1746
|
+
var OPUS_FRAME_SIZE = OPUS_SAMPLE_RATE * OPUS_FRAME_DURATION_MS / 1e3;
|
|
1747
|
+
var OpusEncoderClass = null;
|
|
1748
|
+
function getOpusEncoder() {
|
|
1749
|
+
if (!OpusEncoderClass) {
|
|
1750
|
+
try {
|
|
1751
|
+
const opus = require("@discordjs/opus");
|
|
1752
|
+
OpusEncoderClass = opus.OpusEncoder;
|
|
1753
|
+
} catch {
|
|
1754
|
+
try {
|
|
1755
|
+
const OpusScript = require("opusscript");
|
|
1756
|
+
OpusEncoderClass = OpusScript;
|
|
1757
|
+
} catch {
|
|
1758
|
+
throw new Error(
|
|
1759
|
+
"No Opus library found. Install @discordjs/opus (recommended) or opusscript (WASM fallback)."
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return OpusEncoderClass;
|
|
1765
|
+
}
|
|
1766
|
+
var OpusEncoder = class {
|
|
1767
|
+
encoder;
|
|
1768
|
+
channels;
|
|
1769
|
+
/**
|
|
1770
|
+
* Create an Opus encoder.
|
|
1771
|
+
* @param sampleRate Must be 48000 (Opus native rate)
|
|
1772
|
+
* @param channels Number of channels (1 = mono, 2 = stereo)
|
|
1773
|
+
* @param bitrate Target bitrate in bps (default: 64000)
|
|
1774
|
+
*/
|
|
1775
|
+
constructor(sampleRate = OPUS_SAMPLE_RATE, channels = 1, bitrate = 64e3) {
|
|
1776
|
+
if (sampleRate !== OPUS_SAMPLE_RATE) {
|
|
1777
|
+
throw new Error(`Opus encoder requires ${OPUS_SAMPLE_RATE}Hz, got ${sampleRate}Hz. Resample first.`);
|
|
1778
|
+
}
|
|
1779
|
+
this.channels = channels;
|
|
1780
|
+
const Encoder = getOpusEncoder();
|
|
1781
|
+
this.encoder = new Encoder(sampleRate, channels);
|
|
1782
|
+
if (typeof this.encoder.setBitrate === "function") {
|
|
1783
|
+
this.encoder.setBitrate(bitrate);
|
|
1784
|
+
}
|
|
1785
|
+
log3.debug(`Created Opus encoder: ${sampleRate}Hz, ${channels}ch, ${bitrate}bps`);
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Encode a PCM16 frame to Opus.
|
|
1789
|
+
* @param pcm PCM16 samples (Int16Array or Buffer). Must be exactly OPUS_FRAME_SIZE * channels samples.
|
|
1790
|
+
* @returns Opus-encoded bytes
|
|
1791
|
+
*/
|
|
1792
|
+
encode(pcm) {
|
|
1793
|
+
const buf = pcm instanceof Int16Array ? Buffer.from(pcm.buffer, pcm.byteOffset, pcm.byteLength) : pcm;
|
|
1794
|
+
return this.encoder.encode(buf, OPUS_FRAME_SIZE);
|
|
1795
|
+
}
|
|
1796
|
+
/** Clean up native resources */
|
|
1797
|
+
destroy() {
|
|
1798
|
+
if (this.encoder && typeof this.encoder.delete === "function") {
|
|
1799
|
+
this.encoder.delete();
|
|
1800
|
+
}
|
|
1801
|
+
this.encoder = null;
|
|
1802
|
+
}
|
|
1803
|
+
};
|
|
1804
|
+
|
|
1805
|
+
// src/audio/opus-decoder.ts
|
|
1806
|
+
var log4 = createLogger("OpusDecoder");
|
|
1807
|
+
var OpusDecoderClass = null;
|
|
1808
|
+
function getOpusDecoder() {
|
|
1809
|
+
if (!OpusDecoderClass) {
|
|
1810
|
+
try {
|
|
1811
|
+
OpusDecoderClass = require("opusscript");
|
|
1812
|
+
} catch {
|
|
1813
|
+
throw new Error(
|
|
1814
|
+
"opusscript is required for Opus decoding. Install it: npm install opusscript"
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
return OpusDecoderClass;
|
|
1819
|
+
}
|
|
1820
|
+
var OpusDecoder = class {
|
|
1821
|
+
decoder;
|
|
1822
|
+
channels;
|
|
1823
|
+
/**
|
|
1824
|
+
* Create an Opus decoder.
|
|
1825
|
+
* @param sampleRate Must be 48000 (Opus native rate)
|
|
1826
|
+
* @param channels Number of channels (1 = mono, 2 = stereo)
|
|
1827
|
+
*/
|
|
1828
|
+
constructor(sampleRate = OPUS_SAMPLE_RATE, channels = 1) {
|
|
1829
|
+
if (sampleRate !== OPUS_SAMPLE_RATE) {
|
|
1830
|
+
throw new Error(`Opus decoder requires ${OPUS_SAMPLE_RATE}Hz, got ${sampleRate}Hz`);
|
|
1831
|
+
}
|
|
1832
|
+
this.channels = channels;
|
|
1833
|
+
const Decoder = getOpusDecoder();
|
|
1834
|
+
this.decoder = new Decoder(sampleRate, channels);
|
|
1835
|
+
log4.debug(`Created Opus decoder: ${sampleRate}Hz, ${channels}ch`);
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* Decode an Opus frame to PCM16.
|
|
1839
|
+
* @param opus Opus-encoded bytes
|
|
1840
|
+
* @returns PCM16 samples as Buffer (OPUS_FRAME_SIZE * channels * 2 bytes)
|
|
1841
|
+
*/
|
|
1842
|
+
decode(opus) {
|
|
1843
|
+
return this.decoder.decode(opus);
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Decode an Opus frame to Int16Array.
|
|
1847
|
+
* @param opus Opus-encoded bytes
|
|
1848
|
+
* @returns PCM16 samples
|
|
1849
|
+
*/
|
|
1850
|
+
decodeToInt16Array(opus) {
|
|
1851
|
+
const decoded = this.decode(opus);
|
|
1852
|
+
return new Int16Array(
|
|
1853
|
+
decoded.buffer,
|
|
1854
|
+
decoded.byteOffset,
|
|
1855
|
+
decoded.byteLength / 2
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
/** Generate a silence frame (packet loss concealment) */
|
|
1859
|
+
decodeMissing() {
|
|
1860
|
+
return Buffer.alloc(OPUS_FRAME_SIZE * this.channels * 2);
|
|
1861
|
+
}
|
|
1862
|
+
/** Clean up resources */
|
|
1863
|
+
destroy() {
|
|
1864
|
+
if (this.decoder && typeof this.decoder.delete === "function") {
|
|
1865
|
+
this.decoder.delete();
|
|
1866
|
+
}
|
|
1867
|
+
this.decoder = null;
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
|
|
1871
|
+
// src/audio/rtp-opus.ts
|
|
1872
|
+
var log5 = createLogger("RTP-Opus");
|
|
1873
|
+
var OPUS_PAYLOAD_TYPE = 111;
|
|
1874
|
+
var OPUS_TIMESTAMP_INCREMENT = OPUS_FRAME_SIZE;
|
|
1875
|
+
var OpusRtpPacketizer = class {
|
|
1876
|
+
sequenceNumber;
|
|
1877
|
+
timestamp;
|
|
1878
|
+
ssrc;
|
|
1879
|
+
payloadType;
|
|
1880
|
+
constructor(ssrc = 0, payloadType = OPUS_PAYLOAD_TYPE) {
|
|
1881
|
+
this.sequenceNumber = Math.floor(Math.random() * 65535);
|
|
1882
|
+
this.timestamp = Math.floor(Math.random() * 4294967295);
|
|
1883
|
+
this.ssrc = ssrc;
|
|
1884
|
+
this.payloadType = payloadType;
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Get the next RTP header values for an Opus frame.
|
|
1888
|
+
* Call this for each 20ms frame to get incrementing seq/ts.
|
|
1889
|
+
*/
|
|
1890
|
+
nextPacketInfo() {
|
|
1891
|
+
const info = {
|
|
1892
|
+
sequenceNumber: this.sequenceNumber & 65535,
|
|
1893
|
+
timestamp: this.timestamp >>> 0,
|
|
1894
|
+
ssrc: this.ssrc
|
|
1895
|
+
};
|
|
1896
|
+
this.sequenceNumber = this.sequenceNumber + 1 & 65535;
|
|
1897
|
+
this.timestamp = this.timestamp + OPUS_TIMESTAMP_INCREMENT >>> 0;
|
|
1898
|
+
return info;
|
|
1899
|
+
}
|
|
1900
|
+
/** Reset the packetizer state */
|
|
1901
|
+
reset() {
|
|
1902
|
+
this.sequenceNumber = Math.floor(Math.random() * 65535);
|
|
1903
|
+
this.timestamp = Math.floor(Math.random() * 4294967295);
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
var OpusRtpDepacketizer = class {
|
|
1907
|
+
lastSequenceNumber = -1;
|
|
1908
|
+
lastTimestamp = -1;
|
|
1909
|
+
lostPackets = 0;
|
|
1910
|
+
/**
|
|
1911
|
+
* Process an incoming RTP packet containing an Opus frame.
|
|
1912
|
+
* Returns the Opus payload and metadata.
|
|
1913
|
+
*/
|
|
1914
|
+
processPacket(payload, sequenceNumber, timestamp) {
|
|
1915
|
+
let lost = 0;
|
|
1916
|
+
const isFirst = this.lastSequenceNumber === -1;
|
|
1917
|
+
if (!isFirst) {
|
|
1918
|
+
const expectedSeq = this.lastSequenceNumber + 1 & 65535;
|
|
1919
|
+
if (sequenceNumber !== expectedSeq) {
|
|
1920
|
+
if (sequenceNumber > this.lastSequenceNumber) {
|
|
1921
|
+
lost = sequenceNumber - this.lastSequenceNumber - 1;
|
|
1922
|
+
} else {
|
|
1923
|
+
lost = 65535 - this.lastSequenceNumber + sequenceNumber;
|
|
1924
|
+
}
|
|
1925
|
+
this.lostPackets += lost;
|
|
1926
|
+
if (lost > 0) {
|
|
1927
|
+
log5.debug(`Lost ${lost} packets (seq ${this.lastSequenceNumber} \u2192 ${sequenceNumber})`);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
this.lastSequenceNumber = sequenceNumber;
|
|
1932
|
+
this.lastTimestamp = timestamp;
|
|
1933
|
+
return {
|
|
1934
|
+
opusFrame: payload,
|
|
1935
|
+
lost,
|
|
1936
|
+
isFirst
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
/** Total packets lost since creation */
|
|
1940
|
+
get totalLost() {
|
|
1941
|
+
return this.lostPackets;
|
|
1942
|
+
}
|
|
1943
|
+
/** Reset depacketizer state */
|
|
1944
|
+
reset() {
|
|
1945
|
+
this.lastSequenceNumber = -1;
|
|
1946
|
+
this.lastTimestamp = -1;
|
|
1947
|
+
this.lostPackets = 0;
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1950
|
+
|
|
1951
|
+
// src/audio/resampler.ts
|
|
1952
|
+
function downsample(input, fromRate, toRate, channels = 1) {
|
|
1953
|
+
if (fromRate === toRate) {
|
|
1954
|
+
return new Int16Array(input);
|
|
1955
|
+
}
|
|
1956
|
+
if (fromRate < toRate) {
|
|
1957
|
+
throw new Error(`downsample: fromRate (${fromRate}) must be >= toRate (${toRate})`);
|
|
1958
|
+
}
|
|
1959
|
+
const ratio = fromRate / toRate;
|
|
1960
|
+
if (!Number.isInteger(ratio)) {
|
|
1961
|
+
return resampleLinear(input, fromRate, toRate, channels);
|
|
1962
|
+
}
|
|
1963
|
+
const inputSamplesPerChannel = input.length / channels;
|
|
1964
|
+
const outputSamplesPerChannel = Math.floor(inputSamplesPerChannel / ratio);
|
|
1965
|
+
const output = new Int16Array(outputSamplesPerChannel * channels);
|
|
1966
|
+
for (let ch = 0; ch < channels; ch++) {
|
|
1967
|
+
for (let i = 0; i < outputSamplesPerChannel; i++) {
|
|
1968
|
+
let sum = 0;
|
|
1969
|
+
for (let j = 0; j < ratio; j++) {
|
|
1970
|
+
sum += input[(i * ratio + j) * channels + ch];
|
|
1971
|
+
}
|
|
1972
|
+
output[i * channels + ch] = Math.round(sum / ratio);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
return output;
|
|
1976
|
+
}
|
|
1977
|
+
function upsample(input, fromRate, toRate, channels = 1) {
|
|
1978
|
+
if (fromRate === toRate) {
|
|
1979
|
+
return new Int16Array(input);
|
|
1980
|
+
}
|
|
1981
|
+
if (fromRate > toRate) {
|
|
1982
|
+
throw new Error(`upsample: fromRate (${fromRate}) must be <= toRate (${toRate})`);
|
|
1983
|
+
}
|
|
1984
|
+
const ratio = toRate / fromRate;
|
|
1985
|
+
if (!Number.isInteger(ratio)) {
|
|
1986
|
+
return resampleLinear(input, fromRate, toRate, channels);
|
|
1987
|
+
}
|
|
1988
|
+
const inputSamplesPerChannel = input.length / channels;
|
|
1989
|
+
const outputSamplesPerChannel = inputSamplesPerChannel * ratio;
|
|
1990
|
+
const output = new Int16Array(outputSamplesPerChannel * channels);
|
|
1991
|
+
for (let ch = 0; ch < channels; ch++) {
|
|
1992
|
+
for (let i = 0; i < inputSamplesPerChannel; i++) {
|
|
1993
|
+
const currentSample = input[i * channels + ch];
|
|
1994
|
+
const nextSample = i + 1 < inputSamplesPerChannel ? input[(i + 1) * channels + ch] : currentSample;
|
|
1995
|
+
for (let j = 0; j < ratio; j++) {
|
|
1996
|
+
const t = j / ratio;
|
|
1997
|
+
const interpolated = currentSample + (nextSample - currentSample) * t;
|
|
1998
|
+
output[(i * ratio + j) * channels + ch] = Math.round(interpolated);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
return output;
|
|
2003
|
+
}
|
|
2004
|
+
function resampleLinear(input, fromRate, toRate, channels) {
|
|
2005
|
+
const inputSamplesPerChannel = input.length / channels;
|
|
2006
|
+
const outputSamplesPerChannel = Math.round(inputSamplesPerChannel * toRate / fromRate);
|
|
2007
|
+
const output = new Int16Array(outputSamplesPerChannel * channels);
|
|
2008
|
+
const ratio = fromRate / toRate;
|
|
2009
|
+
for (let ch = 0; ch < channels; ch++) {
|
|
2010
|
+
for (let i = 0; i < outputSamplesPerChannel; i++) {
|
|
2011
|
+
const srcPos = i * ratio;
|
|
2012
|
+
const srcIndex = Math.floor(srcPos);
|
|
2013
|
+
const frac = srcPos - srcIndex;
|
|
2014
|
+
const s0 = srcIndex < inputSamplesPerChannel ? input[srcIndex * channels + ch] : 0;
|
|
2015
|
+
const s1 = srcIndex + 1 < inputSamplesPerChannel ? input[(srcIndex + 1) * channels + ch] : s0;
|
|
2016
|
+
output[i * channels + ch] = Math.round(s0 + (s1 - s0) * frac);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
return output;
|
|
2020
|
+
}
|
|
2021
|
+
function resample(input, fromRate, toRate, channels = 1) {
|
|
2022
|
+
if (fromRate === toRate) return new Int16Array(input);
|
|
2023
|
+
if (fromRate > toRate) return downsample(input, fromRate, toRate, channels);
|
|
2024
|
+
return upsample(input, fromRate, toRate, channels);
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// src/utils/queue.ts
|
|
2028
|
+
var AsyncQueue = class {
|
|
2029
|
+
buffer = [];
|
|
2030
|
+
resolvers = [];
|
|
2031
|
+
closed = false;
|
|
2032
|
+
/** Push an item into the queue. */
|
|
2033
|
+
push(item) {
|
|
2034
|
+
if (this.closed) return;
|
|
2035
|
+
if (this.resolvers.length > 0) {
|
|
2036
|
+
const resolve = this.resolvers.shift();
|
|
2037
|
+
resolve({ value: item, done: false });
|
|
2038
|
+
} else {
|
|
2039
|
+
this.buffer.push(item);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
/** Close the queue. Pending consumers receive done. */
|
|
2043
|
+
close() {
|
|
2044
|
+
this.closed = true;
|
|
2045
|
+
for (const resolve of this.resolvers) {
|
|
2046
|
+
resolve({ value: void 0, done: true });
|
|
2047
|
+
}
|
|
2048
|
+
this.resolvers.length = 0;
|
|
2049
|
+
}
|
|
2050
|
+
/** Number of buffered items. */
|
|
2051
|
+
get size() {
|
|
2052
|
+
return this.buffer.length;
|
|
2053
|
+
}
|
|
2054
|
+
/** Whether the queue is closed. */
|
|
2055
|
+
get isClosed() {
|
|
2056
|
+
return this.closed;
|
|
2057
|
+
}
|
|
2058
|
+
[Symbol.asyncIterator]() {
|
|
2059
|
+
return {
|
|
2060
|
+
next: () => {
|
|
2061
|
+
if (this.buffer.length > 0) {
|
|
2062
|
+
return Promise.resolve({ value: this.buffer.shift(), done: false });
|
|
2063
|
+
}
|
|
2064
|
+
if (this.closed) {
|
|
2065
|
+
return Promise.resolve({ value: void 0, done: true });
|
|
2066
|
+
}
|
|
2067
|
+
return new Promise((resolve) => {
|
|
2068
|
+
this.resolvers.push(resolve);
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
};
|
|
2074
|
+
|
|
2075
|
+
// src/audio/audio-stream.ts
|
|
2076
|
+
var log6 = createLogger("AudioStream");
|
|
2077
|
+
var AudioStream = class {
|
|
2078
|
+
decoder = null;
|
|
2079
|
+
depacketizer;
|
|
2080
|
+
queue;
|
|
2081
|
+
outputSampleRate;
|
|
2082
|
+
outputChannels;
|
|
2083
|
+
track = null;
|
|
2084
|
+
_closed = false;
|
|
2085
|
+
/**
|
|
2086
|
+
* @param track The remote audio track to stream from
|
|
2087
|
+
* @param sampleRate Desired output sample rate (default: 16000 for STT)
|
|
2088
|
+
* @param channels Desired output channels (default: 1 = mono)
|
|
2089
|
+
*/
|
|
2090
|
+
constructor(track, sampleRate = 16e3, channels = 1) {
|
|
2091
|
+
this.outputSampleRate = sampleRate;
|
|
2092
|
+
this.outputChannels = channels;
|
|
2093
|
+
this.depacketizer = new OpusRtpDepacketizer();
|
|
2094
|
+
this.queue = new AsyncQueue();
|
|
2095
|
+
this.track = track;
|
|
2096
|
+
this.start();
|
|
2097
|
+
}
|
|
2098
|
+
get closed() {
|
|
2099
|
+
return this._closed;
|
|
2100
|
+
}
|
|
2101
|
+
/** Close the stream and release resources */
|
|
2102
|
+
close() {
|
|
2103
|
+
if (this._closed) return;
|
|
2104
|
+
this._closed = true;
|
|
2105
|
+
this.queue.close();
|
|
2106
|
+
if (this.decoder) {
|
|
2107
|
+
this.decoder.destroy();
|
|
2108
|
+
this.decoder = null;
|
|
2109
|
+
}
|
|
2110
|
+
this.track = null;
|
|
2111
|
+
log6.debug("AudioStream closed");
|
|
2112
|
+
}
|
|
2113
|
+
[Symbol.asyncIterator]() {
|
|
2114
|
+
return this.queue[Symbol.asyncIterator]();
|
|
2115
|
+
}
|
|
2116
|
+
start() {
|
|
2117
|
+
if (!this.track) return;
|
|
2118
|
+
this.track.onReceiveRtp.subscribe((rtpPacket) => {
|
|
2119
|
+
if (this._closed) return;
|
|
2120
|
+
try {
|
|
2121
|
+
this.processRtpPacket(rtpPacket);
|
|
2122
|
+
} catch (err) {
|
|
2123
|
+
log6.error("Failed to process RTP packet", err);
|
|
2124
|
+
}
|
|
2125
|
+
});
|
|
2126
|
+
this.track.onReceiveRtp.once(() => {
|
|
2127
|
+
});
|
|
2128
|
+
log6.debug(`AudioStream started, output: ${this.outputSampleRate}Hz ${this.outputChannels}ch`);
|
|
2129
|
+
}
|
|
2130
|
+
processRtpPacket(rtp) {
|
|
2131
|
+
if (!rtp.payload || rtp.payload.length === 0) {
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
const payloadCopy = Buffer.from(rtp.payload);
|
|
2135
|
+
if (payloadCopy.length > 1500) {
|
|
2136
|
+
log6.warn(`Skipping oversized RTP payload: ${payloadCopy.length} bytes`);
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
if (!this.decoder) {
|
|
2140
|
+
this.decoder = new OpusDecoder(OPUS_SAMPLE_RATE, this.outputChannels);
|
|
2141
|
+
}
|
|
2142
|
+
const { opusFrame, lost } = this.depacketizer.processPacket(
|
|
2143
|
+
payloadCopy,
|
|
2144
|
+
rtp.header.sequenceNumber,
|
|
2145
|
+
rtp.header.timestamp
|
|
2146
|
+
);
|
|
2147
|
+
for (let i = 0; i < lost && i < 3; i++) {
|
|
2148
|
+
const silence = Buffer.alloc(OPUS_FRAME_SIZE * this.outputChannels * 2);
|
|
2149
|
+
this.emitFrame(silence);
|
|
2150
|
+
}
|
|
2151
|
+
if (opusFrame.length < 1) {
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
try {
|
|
2155
|
+
const opusCopy = Buffer.alloc(opusFrame.length);
|
|
2156
|
+
opusFrame.copy(opusCopy);
|
|
2157
|
+
const pcmBuffer = this.decoder.decode(opusCopy);
|
|
2158
|
+
this.emitFrame(pcmBuffer);
|
|
2159
|
+
} catch (err) {
|
|
2160
|
+
log6.error(`Opus decode failed (${opusFrame.length} bytes)`, err);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
emitFrame(pcm48k) {
|
|
2164
|
+
let samples = new Int16Array(
|
|
2165
|
+
pcm48k.buffer,
|
|
2166
|
+
pcm48k.byteOffset,
|
|
2167
|
+
pcm48k.byteLength / 2
|
|
2168
|
+
);
|
|
2169
|
+
if (this.outputSampleRate !== OPUS_SAMPLE_RATE) {
|
|
2170
|
+
samples = downsample(samples, OPUS_SAMPLE_RATE, this.outputSampleRate, this.outputChannels);
|
|
2171
|
+
}
|
|
2172
|
+
const samplesPerChannel = samples.length / this.outputChannels;
|
|
2173
|
+
const frame = new AudioFrame(samples, this.outputSampleRate, this.outputChannels, samplesPerChannel);
|
|
2174
|
+
this.queue.push(frame);
|
|
2175
|
+
}
|
|
2176
|
+
};
|
|
2177
|
+
|
|
2178
|
+
// src/track.ts
|
|
2179
|
+
var log7 = createLogger("Track");
|
|
2180
|
+
var TrackPublication = class extends TypedEmitter {
|
|
2181
|
+
sid;
|
|
2182
|
+
name;
|
|
2183
|
+
kind;
|
|
2184
|
+
source;
|
|
2185
|
+
mimeType;
|
|
2186
|
+
muted;
|
|
2187
|
+
constructor(info) {
|
|
2188
|
+
super();
|
|
2189
|
+
this.sid = info.sid;
|
|
2190
|
+
this.name = info.name;
|
|
2191
|
+
this.kind = info.type;
|
|
2192
|
+
this.source = info.source;
|
|
2193
|
+
this.mimeType = info.mimeType;
|
|
2194
|
+
this.muted = info.muted;
|
|
2195
|
+
}
|
|
2196
|
+
updateInfo(info) {
|
|
2197
|
+
const wasMuted = this.muted;
|
|
2198
|
+
this.sid = info.sid;
|
|
2199
|
+
this.name = info.name;
|
|
2200
|
+
this.kind = info.type;
|
|
2201
|
+
this.source = info.source;
|
|
2202
|
+
this.mimeType = info.mimeType;
|
|
2203
|
+
this.muted = info.muted;
|
|
2204
|
+
if (wasMuted !== info.muted) {
|
|
2205
|
+
this.emit(info.muted ? "muted" : "unmuted");
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
2209
|
+
var LocalTrackPublication = class extends TrackPublication {
|
|
2210
|
+
track;
|
|
2211
|
+
constructor(info, track) {
|
|
2212
|
+
super(info);
|
|
2213
|
+
this.track = track;
|
|
2214
|
+
}
|
|
2215
|
+
};
|
|
2216
|
+
var RemoteTrackPublication = class extends TrackPublication {
|
|
2217
|
+
track = null;
|
|
2218
|
+
setTrack(track) {
|
|
2219
|
+
this.track = track;
|
|
2220
|
+
}
|
|
2221
|
+
};
|
|
2222
|
+
var LocalAudioTrack = class _LocalAudioTrack {
|
|
2223
|
+
name;
|
|
2224
|
+
source;
|
|
2225
|
+
/** werift MediaStreamTrack used by the PeerConnection sender */
|
|
2226
|
+
mediaTrack;
|
|
2227
|
+
transceiver = null;
|
|
2228
|
+
packetizer;
|
|
2229
|
+
_cid;
|
|
2230
|
+
_sid = "";
|
|
2231
|
+
constructor(name, source) {
|
|
2232
|
+
this.name = name;
|
|
2233
|
+
this.source = source;
|
|
2234
|
+
this.mediaTrack = new import_werift2.MediaStreamTrack({ kind: "audio" });
|
|
2235
|
+
this.packetizer = new OpusRtpPacketizer(0, OPUS_PAYLOAD_TYPE);
|
|
2236
|
+
this._cid = `track-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2237
|
+
}
|
|
2238
|
+
/** Create a local audio track from an AudioSource */
|
|
2239
|
+
static createAudioTrack(name, source) {
|
|
2240
|
+
return new _LocalAudioTrack(name, source);
|
|
2241
|
+
}
|
|
2242
|
+
/** Client-generated track ID (used before server assigns SID) */
|
|
2243
|
+
get cid() {
|
|
2244
|
+
return this._cid;
|
|
2245
|
+
}
|
|
2246
|
+
/** Server-assigned track SID */
|
|
2247
|
+
get sid() {
|
|
2248
|
+
return this._sid;
|
|
2249
|
+
}
|
|
2250
|
+
set sid(value) {
|
|
2251
|
+
this._sid = value;
|
|
2252
|
+
}
|
|
2253
|
+
/**
|
|
2254
|
+
* Set the RTP transceiver for sending audio.
|
|
2255
|
+
* Called internally by LocalParticipant after negotiation.
|
|
2256
|
+
*/
|
|
2257
|
+
setTransceiver(transceiver) {
|
|
2258
|
+
this.transceiver = transceiver;
|
|
2259
|
+
this.source.onEncodedFrame = (opusData) => {
|
|
2260
|
+
if (this.mediaTrack.stopped) return;
|
|
2261
|
+
try {
|
|
2262
|
+
const info = this.packetizer.nextPacketInfo();
|
|
2263
|
+
const header = new import_werift2.RtpHeader();
|
|
2264
|
+
header.payloadType = OPUS_PAYLOAD_TYPE;
|
|
2265
|
+
header.sequenceNumber = info.sequenceNumber;
|
|
2266
|
+
header.timestamp = info.timestamp;
|
|
2267
|
+
header.ssrc = info.ssrc;
|
|
2268
|
+
header.marker = false;
|
|
2269
|
+
const packet = new import_werift2.RtpPacket(header, opusData);
|
|
2270
|
+
this.mediaTrack.writeRtp(packet);
|
|
2271
|
+
} catch (err) {
|
|
2272
|
+
log7.error("Failed to send RTP via track", err);
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
/** Stop the track and release resources */
|
|
2277
|
+
stop() {
|
|
2278
|
+
this.source.flush();
|
|
2279
|
+
this.source.destroy();
|
|
2280
|
+
this.mediaTrack.stop();
|
|
2281
|
+
this.transceiver = null;
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
var RemoteAudioTrack = class extends TypedEmitter {
|
|
2285
|
+
sid;
|
|
2286
|
+
name;
|
|
2287
|
+
mediaTrack;
|
|
2288
|
+
_streams = [];
|
|
2289
|
+
constructor(sid, name, mediaTrack) {
|
|
2290
|
+
super();
|
|
2291
|
+
this.sid = sid;
|
|
2292
|
+
this.name = name;
|
|
2293
|
+
this.mediaTrack = mediaTrack;
|
|
2294
|
+
}
|
|
2295
|
+
/**
|
|
2296
|
+
* Create an AudioStream to consume decoded PCM16 frames from this track.
|
|
2297
|
+
* @param sampleRate Desired output sample rate (default: 16000 for STT)
|
|
2298
|
+
* @param channels Desired channels (default: 1)
|
|
2299
|
+
*/
|
|
2300
|
+
createStream(sampleRate = 16e3, channels = 1) {
|
|
2301
|
+
const stream = new AudioStream(this.mediaTrack, sampleRate, channels);
|
|
2302
|
+
this._streams.push(stream);
|
|
2303
|
+
return stream;
|
|
2304
|
+
}
|
|
2305
|
+
/** Close all streams and release resources */
|
|
2306
|
+
stop() {
|
|
2307
|
+
for (const stream of this._streams) {
|
|
2308
|
+
stream.close();
|
|
2309
|
+
}
|
|
2310
|
+
this._streams = [];
|
|
2311
|
+
this.emit("ended");
|
|
2312
|
+
}
|
|
2313
|
+
};
|
|
2314
|
+
|
|
2315
|
+
// src/participant.ts
|
|
2316
|
+
var log8 = createLogger("Participant");
|
|
2317
|
+
var Participant = class extends TypedEmitter {
|
|
2318
|
+
sid;
|
|
2319
|
+
identity;
|
|
2320
|
+
name;
|
|
2321
|
+
metadata;
|
|
2322
|
+
state;
|
|
2323
|
+
_trackPublications = /* @__PURE__ */ new Map();
|
|
2324
|
+
constructor(sid, identity, name = "", metadata = "") {
|
|
2325
|
+
super();
|
|
2326
|
+
this.sid = sid;
|
|
2327
|
+
this.identity = identity;
|
|
2328
|
+
this.name = name;
|
|
2329
|
+
this.metadata = metadata;
|
|
2330
|
+
this.state = 0 /* JOINING */;
|
|
2331
|
+
}
|
|
2332
|
+
get trackPublications() {
|
|
2333
|
+
return this._trackPublications;
|
|
2334
|
+
}
|
|
2335
|
+
/** Update participant info from server */
|
|
2336
|
+
updateInfo(info) {
|
|
2337
|
+
const metadataChanged = this.metadata !== info.metadata;
|
|
2338
|
+
this.sid = info.sid;
|
|
2339
|
+
this.identity = info.identity;
|
|
2340
|
+
this.name = info.name;
|
|
2341
|
+
this.metadata = info.metadata;
|
|
2342
|
+
this.state = info.state;
|
|
2343
|
+
if (metadataChanged) {
|
|
2344
|
+
this.emit("metadataChanged", info.metadata);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
};
|
|
2348
|
+
var LocalParticipant = class extends Participant {
|
|
2349
|
+
engine;
|
|
2350
|
+
publishedTracks = /* @__PURE__ */ new Map();
|
|
2351
|
+
constructor(engine, sid, identity, name = "", metadata = "") {
|
|
2352
|
+
super(sid, identity, name, metadata);
|
|
2353
|
+
this.engine = engine;
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* Publish an audio track to the room.
|
|
2357
|
+
*
|
|
2358
|
+
* Flow:
|
|
2359
|
+
* 1. Send AddTrackRequest to server
|
|
2360
|
+
* 2. Wait for TrackPublishedResponse (server assigns SID)
|
|
2361
|
+
* 3. Add transceiver to publisher PeerConnection
|
|
2362
|
+
* 4. Negotiate SDP
|
|
2363
|
+
*/
|
|
2364
|
+
async publishTrack(track, options) {
|
|
2365
|
+
const name = options?.name ?? track.name;
|
|
2366
|
+
const source = options?.source ?? 2 /* MICROPHONE */;
|
|
2367
|
+
const disableDtx = options?.disableDtx ?? false;
|
|
2368
|
+
log8.info(`Publishing track "${name}" (cid=${track.cid})`);
|
|
2369
|
+
const response = await this.engine.requestPublishTrack(
|
|
2370
|
+
track.cid,
|
|
2371
|
+
name,
|
|
2372
|
+
0 /* AUDIO */,
|
|
2373
|
+
source,
|
|
2374
|
+
{ disableDtx }
|
|
2375
|
+
);
|
|
2376
|
+
if (!response.track) {
|
|
2377
|
+
throw new Error("Server did not return track info");
|
|
2378
|
+
}
|
|
2379
|
+
track.sid = response.track.sid;
|
|
2380
|
+
log8.debug(`Track published: cid=${track.cid}, sid=${track.sid}`);
|
|
2381
|
+
const transceiver = await this.engine.addTransceiver(track.mediaTrack);
|
|
2382
|
+
track.setTransceiver(transceiver);
|
|
2383
|
+
await this.engine.negotiate();
|
|
2384
|
+
const publication = new LocalTrackPublication(response.track, track);
|
|
2385
|
+
this._trackPublications.set(response.track.sid, publication);
|
|
2386
|
+
this.publishedTracks.set(track.cid, track);
|
|
2387
|
+
this.emit("trackPublished", publication);
|
|
2388
|
+
return publication;
|
|
2389
|
+
}
|
|
2390
|
+
/**
|
|
2391
|
+
* Unpublish an audio track from the room.
|
|
2392
|
+
*/
|
|
2393
|
+
async unpublishTrack(track) {
|
|
2394
|
+
log8.info(`Unpublishing track "${track.name}" (sid=${track.sid})`);
|
|
2395
|
+
track.stop();
|
|
2396
|
+
this._trackPublications.delete(track.sid);
|
|
2397
|
+
this.publishedTracks.delete(track.cid);
|
|
2398
|
+
await this.engine.negotiate();
|
|
2399
|
+
this.emit("trackUnpublished", new TrackPublication({
|
|
2400
|
+
sid: track.sid,
|
|
2401
|
+
name: track.name,
|
|
2402
|
+
type: 0 /* AUDIO */,
|
|
2403
|
+
source: 2 /* MICROPHONE */,
|
|
2404
|
+
muted: false,
|
|
2405
|
+
width: 0,
|
|
2406
|
+
height: 0,
|
|
2407
|
+
simulcast: false,
|
|
2408
|
+
disableDtx: false,
|
|
2409
|
+
layers: [],
|
|
2410
|
+
mimeType: "audio/opus",
|
|
2411
|
+
mid: ""
|
|
2412
|
+
}));
|
|
2413
|
+
}
|
|
2414
|
+
/**
|
|
2415
|
+
* Publish data to the room.
|
|
2416
|
+
*
|
|
2417
|
+
* @param data The data payload
|
|
2418
|
+
* @param options Delivery options (kind, destinations, topic)
|
|
2419
|
+
*/
|
|
2420
|
+
async publishData(data, options = {}) {
|
|
2421
|
+
const kind = options.kind ?? 0 /* RELIABLE */;
|
|
2422
|
+
const destinationSids = options.destinationSids ?? [];
|
|
2423
|
+
const topic = options.topic;
|
|
2424
|
+
const packet = {
|
|
2425
|
+
kind,
|
|
2426
|
+
user: {
|
|
2427
|
+
participantSid: this.sid,
|
|
2428
|
+
payload: data,
|
|
2429
|
+
destinationSids,
|
|
2430
|
+
topic
|
|
2431
|
+
}
|
|
2432
|
+
};
|
|
2433
|
+
const encoded = DataPacket.encode(packet).finish();
|
|
2434
|
+
const channelKind = kind === 0 /* RELIABLE */ ? "reliable" : "lossy";
|
|
2435
|
+
this.engine.sendData(new Uint8Array(encoded), channelKind);
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
var RemoteParticipant = class extends TypedEmitter {
|
|
2439
|
+
sid;
|
|
2440
|
+
identity;
|
|
2441
|
+
name;
|
|
2442
|
+
metadata;
|
|
2443
|
+
state;
|
|
2444
|
+
_trackPublications = /* @__PURE__ */ new Map();
|
|
2445
|
+
_audioTracks = /* @__PURE__ */ new Map();
|
|
2446
|
+
constructor(info) {
|
|
2447
|
+
super();
|
|
2448
|
+
this.sid = info.sid;
|
|
2449
|
+
this.identity = info.identity;
|
|
2450
|
+
this.name = info.name;
|
|
2451
|
+
this.metadata = info.metadata;
|
|
2452
|
+
this.state = info.state;
|
|
2453
|
+
for (const trackInfo of info.tracks) {
|
|
2454
|
+
const pub = new RemoteTrackPublication(trackInfo);
|
|
2455
|
+
this._trackPublications.set(trackInfo.sid, pub);
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
get trackPublications() {
|
|
2459
|
+
return this._trackPublications;
|
|
2460
|
+
}
|
|
2461
|
+
get audioTracks() {
|
|
2462
|
+
return this._audioTracks;
|
|
2463
|
+
}
|
|
2464
|
+
/** Update participant info from server */
|
|
2465
|
+
updateInfo(info) {
|
|
2466
|
+
const metadataChanged = this.metadata !== info.metadata;
|
|
2467
|
+
this.sid = info.sid;
|
|
2468
|
+
this.identity = info.identity;
|
|
2469
|
+
this.name = info.name;
|
|
2470
|
+
this.metadata = info.metadata;
|
|
2471
|
+
this.state = info.state;
|
|
2472
|
+
const activeSids = /* @__PURE__ */ new Set();
|
|
2473
|
+
for (const trackInfo of info.tracks) {
|
|
2474
|
+
activeSids.add(trackInfo.sid);
|
|
2475
|
+
const existing = this._trackPublications.get(trackInfo.sid);
|
|
2476
|
+
if (existing) {
|
|
2477
|
+
existing.updateInfo(trackInfo);
|
|
2478
|
+
} else {
|
|
2479
|
+
const pub = new RemoteTrackPublication(trackInfo);
|
|
2480
|
+
this._trackPublications.set(trackInfo.sid, pub);
|
|
2481
|
+
this.emit("trackPublished", pub);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
for (const [sid, pub] of this._trackPublications) {
|
|
2485
|
+
if (!activeSids.has(sid)) {
|
|
2486
|
+
this._trackPublications.delete(sid);
|
|
2487
|
+
if (pub.track) {
|
|
2488
|
+
this.removeTrack(sid);
|
|
2489
|
+
}
|
|
2490
|
+
this.emit("trackUnpublished", pub);
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
if (metadataChanged) {
|
|
2494
|
+
this.emit("metadataChanged", info.metadata);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
/**
|
|
2498
|
+
* Called when a remote media track is received on the subscriber PC.
|
|
2499
|
+
* Associates the media track with the correct publication.
|
|
2500
|
+
*/
|
|
2501
|
+
addSubscribedTrack(mediaTrack, trackSid, trackName) {
|
|
2502
|
+
const publication = this._trackPublications.get(trackSid);
|
|
2503
|
+
if (!publication) {
|
|
2504
|
+
log8.warn(`No publication found for track ${trackSid}`);
|
|
2505
|
+
const tempPub = new RemoteTrackPublication({
|
|
2506
|
+
sid: trackSid,
|
|
2507
|
+
name: trackName,
|
|
2508
|
+
type: 0 /* AUDIO */,
|
|
2509
|
+
source: 2 /* MICROPHONE */,
|
|
2510
|
+
muted: false,
|
|
2511
|
+
width: 0,
|
|
2512
|
+
height: 0,
|
|
2513
|
+
simulcast: false,
|
|
2514
|
+
disableDtx: false,
|
|
2515
|
+
layers: [],
|
|
2516
|
+
mimeType: "audio/opus",
|
|
2517
|
+
mid: ""
|
|
2518
|
+
});
|
|
2519
|
+
this._trackPublications.set(trackSid, tempPub);
|
|
2520
|
+
}
|
|
2521
|
+
const pub = this._trackPublications.get(trackSid);
|
|
2522
|
+
const remoteTrack = new RemoteAudioTrack(trackSid, pub.name, mediaTrack);
|
|
2523
|
+
pub.setTrack(remoteTrack);
|
|
2524
|
+
this._audioTracks.set(trackSid, remoteTrack);
|
|
2525
|
+
log8.debug(`Track subscribed: ${pub.name} (${trackSid}) from ${this.identity}`);
|
|
2526
|
+
this.emit("trackSubscribed", remoteTrack, pub);
|
|
2527
|
+
return remoteTrack;
|
|
2528
|
+
}
|
|
2529
|
+
/** Remove a subscribed track */
|
|
2530
|
+
removeTrack(trackSid) {
|
|
2531
|
+
const track = this._audioTracks.get(trackSid);
|
|
2532
|
+
if (!track) return null;
|
|
2533
|
+
track.stop();
|
|
2534
|
+
this._audioTracks.delete(trackSid);
|
|
2535
|
+
const pub = this._trackPublications.get(trackSid);
|
|
2536
|
+
if (pub) {
|
|
2537
|
+
pub.setTrack(null);
|
|
2538
|
+
this.emit("trackUnsubscribed", track, pub);
|
|
2539
|
+
}
|
|
2540
|
+
return track;
|
|
2541
|
+
}
|
|
2542
|
+
/** Clean up all tracks */
|
|
2543
|
+
destroy() {
|
|
2544
|
+
for (const [sid] of this._audioTracks) {
|
|
2545
|
+
this.removeTrack(sid);
|
|
2546
|
+
}
|
|
2547
|
+
this._trackPublications.clear();
|
|
2548
|
+
}
|
|
2549
|
+
};
|
|
2550
|
+
|
|
2551
|
+
// src/room.ts
|
|
2552
|
+
var log9 = createLogger("Room");
|
|
2553
|
+
var Room = class extends TypedEmitter {
|
|
2554
|
+
/** Local participant (this bot) */
|
|
2555
|
+
localParticipant;
|
|
2556
|
+
/** Remote participants indexed by SID */
|
|
2557
|
+
remoteParticipants = /* @__PURE__ */ new Map();
|
|
2558
|
+
/** Room name */
|
|
2559
|
+
name = "";
|
|
2560
|
+
/** Room SID */
|
|
2561
|
+
sid = "";
|
|
2562
|
+
/** Room metadata */
|
|
2563
|
+
metadata = "";
|
|
2564
|
+
engine;
|
|
2565
|
+
_isConnected = false;
|
|
2566
|
+
activeSpeakers = [];
|
|
2567
|
+
roomInfo = null;
|
|
2568
|
+
constructor() {
|
|
2569
|
+
super();
|
|
2570
|
+
this.engine = new RTCEngine();
|
|
2571
|
+
}
|
|
2572
|
+
get isConnected() {
|
|
2573
|
+
return this._isConnected;
|
|
2574
|
+
}
|
|
2575
|
+
/**
|
|
2576
|
+
* Connect to a dTelecom room.
|
|
2577
|
+
*
|
|
2578
|
+
* @param url WebSocket URL of the dTelecom server (e.g. "wss://my.dtelecom.org")
|
|
2579
|
+
* @param token JWT access token (from AccessToken in @dtelecom/server-sdk-js)
|
|
2580
|
+
* @param options Room connection options
|
|
2581
|
+
*/
|
|
2582
|
+
async connect(url, token, options = {}) {
|
|
2583
|
+
log9.info("Connecting to room...");
|
|
2584
|
+
const joinResponse = await this.engine.connect(url, token, {
|
|
2585
|
+
autoSubscribe: options.autoSubscribe ?? true,
|
|
2586
|
+
connectTimeout: options.connectTimeout ?? 1e4
|
|
2587
|
+
});
|
|
2588
|
+
this.handleJoinResponse(joinResponse);
|
|
2589
|
+
this.setupEngineHandlers();
|
|
2590
|
+
this.setupSignalHandlers();
|
|
2591
|
+
this._isConnected = true;
|
|
2592
|
+
log9.info(`Connected to room "${this.name}" as "${this.localParticipant.identity}"`);
|
|
2593
|
+
}
|
|
2594
|
+
/**
|
|
2595
|
+
* Disconnect from the room.
|
|
2596
|
+
*/
|
|
2597
|
+
async disconnect() {
|
|
2598
|
+
if (!this._isConnected) return;
|
|
2599
|
+
log9.info("Disconnecting from room...");
|
|
2600
|
+
this._isConnected = false;
|
|
2601
|
+
for (const [sid, participant] of this.remoteParticipants) {
|
|
2602
|
+
participant.destroy();
|
|
2603
|
+
this.remoteParticipants.delete(sid);
|
|
2604
|
+
}
|
|
2605
|
+
await this.engine.disconnect();
|
|
2606
|
+
this.emit("disconnected", "client_initiated");
|
|
2607
|
+
}
|
|
2608
|
+
/** Get a remote participant by SID */
|
|
2609
|
+
getParticipant(sid) {
|
|
2610
|
+
return this.remoteParticipants.get(sid);
|
|
2611
|
+
}
|
|
2612
|
+
/** Get a remote participant by identity */
|
|
2613
|
+
getParticipantByIdentity(identity) {
|
|
2614
|
+
for (const p of this.remoteParticipants.values()) {
|
|
2615
|
+
if (p.identity === identity) return p;
|
|
2616
|
+
}
|
|
2617
|
+
return void 0;
|
|
2618
|
+
}
|
|
2619
|
+
// ─── Private ────────────────────────────────────────────────────────────
|
|
2620
|
+
handleJoinResponse(join) {
|
|
2621
|
+
if (join.room) {
|
|
2622
|
+
this.roomInfo = join.room;
|
|
2623
|
+
this.name = join.room.name;
|
|
2624
|
+
this.sid = join.room.sid;
|
|
2625
|
+
this.metadata = join.room.metadata;
|
|
2626
|
+
}
|
|
2627
|
+
if (join.participant) {
|
|
2628
|
+
this.localParticipant = new LocalParticipant(
|
|
2629
|
+
this.engine,
|
|
2630
|
+
join.participant.sid,
|
|
2631
|
+
join.participant.identity,
|
|
2632
|
+
join.participant.name,
|
|
2633
|
+
join.participant.metadata
|
|
2634
|
+
);
|
|
2635
|
+
}
|
|
2636
|
+
for (const info of join.otherParticipants) {
|
|
2637
|
+
this.getOrCreateParticipant(info);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
setupEngineHandlers() {
|
|
2641
|
+
this.engine.on("connected", () => {
|
|
2642
|
+
log9.info("WebRTC connection established");
|
|
2643
|
+
});
|
|
2644
|
+
this.engine.on("disconnected", (reason) => {
|
|
2645
|
+
if (this._isConnected) {
|
|
2646
|
+
this._isConnected = false;
|
|
2647
|
+
this.emit("disconnected", reason);
|
|
2648
|
+
}
|
|
2649
|
+
});
|
|
2650
|
+
this.engine.on("remoteTrack", (mediaTrack, transceiver) => {
|
|
2651
|
+
this.handleRemoteTrack(mediaTrack, transceiver);
|
|
2652
|
+
});
|
|
2653
|
+
this.engine.on("dataMessage", (data, kind) => {
|
|
2654
|
+
this.handleDataMessage(data, kind);
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
setupSignalHandlers() {
|
|
2658
|
+
this.engine.signal.on("participantUpdate", (update) => {
|
|
2659
|
+
this.handleParticipantUpdate(update);
|
|
2660
|
+
});
|
|
2661
|
+
this.engine.signal.on("speakersChanged", (changed) => {
|
|
2662
|
+
this.handleSpeakersChanged(changed);
|
|
2663
|
+
});
|
|
2664
|
+
this.engine.signal.on("roomUpdate", (update) => {
|
|
2665
|
+
if (update.room) {
|
|
2666
|
+
this.roomInfo = update.room;
|
|
2667
|
+
const oldMetadata = this.metadata;
|
|
2668
|
+
this.metadata = update.room.metadata;
|
|
2669
|
+
if (oldMetadata !== this.metadata) {
|
|
2670
|
+
this.emit("roomMetadataChanged", this.metadata);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
});
|
|
2674
|
+
this.engine.signal.on("tokenRefresh", (token) => {
|
|
2675
|
+
log9.debug("Token refreshed");
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
2678
|
+
handleParticipantUpdate(update) {
|
|
2679
|
+
for (const info of update.participants) {
|
|
2680
|
+
if (info.sid === this.localParticipant.sid || info.identity === this.localParticipant.identity) {
|
|
2681
|
+
this.localParticipant.updateInfo(info);
|
|
2682
|
+
continue;
|
|
2683
|
+
}
|
|
2684
|
+
if (info.state === 3 /* DISCONNECTED */) {
|
|
2685
|
+
const participant = this.remoteParticipants.get(info.sid);
|
|
2686
|
+
if (participant) {
|
|
2687
|
+
participant.destroy();
|
|
2688
|
+
this.remoteParticipants.delete(info.sid);
|
|
2689
|
+
log9.info(`Participant disconnected: ${participant.identity}`);
|
|
2690
|
+
this.emit("participantDisconnected", participant);
|
|
2691
|
+
}
|
|
2692
|
+
} else {
|
|
2693
|
+
const isNew = !this.remoteParticipants.has(info.sid);
|
|
2694
|
+
const participant = this.getOrCreateParticipant(info);
|
|
2695
|
+
if (isNew) {
|
|
2696
|
+
log9.info(`Participant connected: ${participant.identity}`);
|
|
2697
|
+
this.emit("participantConnected", participant);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
handleRemoteTrack(mediaTrack, transceiver) {
|
|
2703
|
+
const mid = transceiver.mid;
|
|
2704
|
+
const mediaKind = mediaTrack?.kind;
|
|
2705
|
+
const expectedType = mediaKind === "audio" ? 0 /* AUDIO */ : 1 /* VIDEO */;
|
|
2706
|
+
for (const participant of this.remoteParticipants.values()) {
|
|
2707
|
+
for (const [sid, pub] of participant.trackPublications) {
|
|
2708
|
+
if (!pub.track && pub.kind === expectedType) {
|
|
2709
|
+
if (mediaKind !== "audio") {
|
|
2710
|
+
log9.debug(`Skipping ${mediaKind} track ${sid} (audio-only SDK)`);
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
participant.addSubscribedTrack(mediaTrack, sid, pub.name);
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
log9.warn(`Received remote track (mid=${mid}, kind=${mediaKind}) but couldn't match to a participant`);
|
|
2719
|
+
}
|
|
2720
|
+
handleDataMessage(data, kind) {
|
|
2721
|
+
try {
|
|
2722
|
+
const packet = DataPacket.decode(data);
|
|
2723
|
+
if (packet.user) {
|
|
2724
|
+
const participant = this.remoteParticipants.get(packet.user.participantSid);
|
|
2725
|
+
const packetKind = kind === "reliable" ? 0 /* RELIABLE */ : 1 /* LOSSY */;
|
|
2726
|
+
this.emit("dataReceived", packet.user.payload, participant, packetKind, packet.user.topic);
|
|
2727
|
+
}
|
|
2728
|
+
} catch (err) {
|
|
2729
|
+
log9.error("Failed to decode data message", err);
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
handleSpeakersChanged(changed) {
|
|
2733
|
+
const speakers = [];
|
|
2734
|
+
for (const speaker of changed.speakers) {
|
|
2735
|
+
if (speaker.sid === this.localParticipant.sid) {
|
|
2736
|
+
speakers.push(this.localParticipant);
|
|
2737
|
+
} else {
|
|
2738
|
+
const p = this.remoteParticipants.get(speaker.sid);
|
|
2739
|
+
if (p) speakers.push(p);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
this.activeSpeakers = speakers;
|
|
2743
|
+
this.emit("activeSpeakersChanged", speakers);
|
|
2744
|
+
}
|
|
2745
|
+
getOrCreateParticipant(info) {
|
|
2746
|
+
let participant = this.remoteParticipants.get(info.sid);
|
|
2747
|
+
if (participant) {
|
|
2748
|
+
participant.updateInfo(info);
|
|
2749
|
+
} else {
|
|
2750
|
+
participant = new RemoteParticipant(info);
|
|
2751
|
+
this.remoteParticipants.set(info.sid, participant);
|
|
2752
|
+
participant.on("trackSubscribed", (track, pub) => {
|
|
2753
|
+
this.emit("trackSubscribed", track, pub, participant);
|
|
2754
|
+
});
|
|
2755
|
+
participant.on("trackUnsubscribed", (track, pub) => {
|
|
2756
|
+
this.emit("trackUnsubscribed", track, pub, participant);
|
|
2757
|
+
});
|
|
2758
|
+
participant.on("trackPublished", (pub) => {
|
|
2759
|
+
this.emit("trackPublished", pub, participant);
|
|
2760
|
+
});
|
|
2761
|
+
participant.on("trackUnpublished", (pub) => {
|
|
2762
|
+
this.emit("trackUnpublished", pub, participant);
|
|
2763
|
+
});
|
|
2764
|
+
}
|
|
2765
|
+
return participant;
|
|
2766
|
+
}
|
|
2767
|
+
};
|
|
2768
|
+
|
|
2769
|
+
// src/audio/audio-source.ts
|
|
2770
|
+
var log10 = createLogger("AudioSource");
|
|
2771
|
+
var AudioSource = class {
|
|
2772
|
+
sampleRate;
|
|
2773
|
+
channels;
|
|
2774
|
+
encoder = null;
|
|
2775
|
+
track = null;
|
|
2776
|
+
// Buffer for accumulating samples to form exact 20ms frames at 48kHz
|
|
2777
|
+
sampleBuffer;
|
|
2778
|
+
bufferOffset = 0;
|
|
2779
|
+
frameSizeAt48k;
|
|
2780
|
+
// Callback set by LocalAudioTrack to receive encoded Opus frames
|
|
2781
|
+
_onEncodedFrame = null;
|
|
2782
|
+
/**
|
|
2783
|
+
* @param sampleRate Input sample rate (e.g. 16000 for STT/TTS)
|
|
2784
|
+
* @param channels Number of channels (1 = mono)
|
|
2785
|
+
*/
|
|
2786
|
+
constructor(sampleRate, channels = 1) {
|
|
2787
|
+
this.sampleRate = sampleRate;
|
|
2788
|
+
this.channels = channels;
|
|
2789
|
+
this.frameSizeAt48k = OPUS_FRAME_SIZE * channels;
|
|
2790
|
+
this.sampleBuffer = new Int16Array(this.frameSizeAt48k);
|
|
2791
|
+
}
|
|
2792
|
+
/** Set the callback for encoded Opus frames. Used internally by LocalAudioTrack. */
|
|
2793
|
+
set onEncodedFrame(cb) {
|
|
2794
|
+
this._onEncodedFrame = cb;
|
|
2795
|
+
}
|
|
2796
|
+
/** Associate this source with a werift MediaStreamTrack */
|
|
2797
|
+
setTrack(track) {
|
|
2798
|
+
this.track = track;
|
|
2799
|
+
}
|
|
2800
|
+
/**
|
|
2801
|
+
* Feed a PCM16 audio frame into the source.
|
|
2802
|
+
*
|
|
2803
|
+
* The frame is resampled to 48kHz, buffered to 20ms boundaries,
|
|
2804
|
+
* Opus-encoded, and sent to the track for RTP transmission.
|
|
2805
|
+
*/
|
|
2806
|
+
async captureFrame(frame) {
|
|
2807
|
+
if (!this.encoder) {
|
|
2808
|
+
this.encoder = new OpusEncoder(OPUS_SAMPLE_RATE, this.channels);
|
|
2809
|
+
}
|
|
2810
|
+
let samples = frame.data;
|
|
2811
|
+
if (frame.sampleRate !== OPUS_SAMPLE_RATE) {
|
|
2812
|
+
samples = upsample(frame.data, frame.sampleRate, OPUS_SAMPLE_RATE, this.channels);
|
|
2813
|
+
}
|
|
2814
|
+
let offset = 0;
|
|
2815
|
+
while (offset < samples.length) {
|
|
2816
|
+
const remaining = this.frameSizeAt48k - this.bufferOffset;
|
|
2817
|
+
const available = samples.length - offset;
|
|
2818
|
+
const toCopy = Math.min(remaining, available);
|
|
2819
|
+
this.sampleBuffer.set(samples.subarray(offset, offset + toCopy), this.bufferOffset);
|
|
2820
|
+
this.bufferOffset += toCopy;
|
|
2821
|
+
offset += toCopy;
|
|
2822
|
+
if (this.bufferOffset >= this.frameSizeAt48k) {
|
|
2823
|
+
this.encodeAndSend(this.sampleBuffer);
|
|
2824
|
+
this.bufferOffset = 0;
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
/** Clear any buffered samples */
|
|
2829
|
+
flush() {
|
|
2830
|
+
if (this.bufferOffset > 0) {
|
|
2831
|
+
this.sampleBuffer.fill(0, this.bufferOffset);
|
|
2832
|
+
this.encodeAndSend(this.sampleBuffer);
|
|
2833
|
+
this.bufferOffset = 0;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
/** Release encoder resources */
|
|
2837
|
+
destroy() {
|
|
2838
|
+
if (this.encoder) {
|
|
2839
|
+
this.encoder.destroy();
|
|
2840
|
+
this.encoder = null;
|
|
2841
|
+
}
|
|
2842
|
+
this._onEncodedFrame = null;
|
|
2843
|
+
this.track = null;
|
|
2844
|
+
}
|
|
2845
|
+
encodeAndSend(pcm) {
|
|
2846
|
+
if (!this.encoder) return;
|
|
2847
|
+
try {
|
|
2848
|
+
const opusData = this.encoder.encode(pcm);
|
|
2849
|
+
if (this._onEncodedFrame) {
|
|
2850
|
+
this._onEncodedFrame(opusData);
|
|
2851
|
+
}
|
|
2852
|
+
} catch (err) {
|
|
2853
|
+
log10.error("Opus encode failed", err);
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
};
|
|
2857
|
+
|
|
2858
|
+
// src/data/data-channel.ts
|
|
2859
|
+
var log11 = createLogger("DataChannel");
|
|
2860
|
+
function encodeDataPacket(participantSid, payload, kind = 0 /* RELIABLE */, options) {
|
|
2861
|
+
const packet = {
|
|
2862
|
+
kind,
|
|
2863
|
+
user: {
|
|
2864
|
+
participantSid,
|
|
2865
|
+
payload,
|
|
2866
|
+
destinationSids: options?.destinationSids ?? [],
|
|
2867
|
+
topic: options?.topic
|
|
2868
|
+
}
|
|
2869
|
+
};
|
|
2870
|
+
return DataPacket.encode(packet).finish();
|
|
2871
|
+
}
|
|
2872
|
+
function decodeDataPacket(data) {
|
|
2873
|
+
return DataPacket.decode(data);
|
|
2874
|
+
}
|
|
2875
|
+
function createTextMessage(participantSid, text, options) {
|
|
2876
|
+
const payload = new TextEncoder().encode(text);
|
|
2877
|
+
return encodeDataPacket(participantSid, payload, 0 /* RELIABLE */, options);
|
|
2878
|
+
}
|
|
2879
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2880
|
+
0 && (module.exports = {
|
|
2881
|
+
AudioFrame,
|
|
2882
|
+
AudioSource,
|
|
2883
|
+
AudioStream,
|
|
2884
|
+
ConnectionQuality,
|
|
2885
|
+
DataPacket_Kind,
|
|
2886
|
+
DisconnectReason,
|
|
2887
|
+
LocalAudioTrack,
|
|
2888
|
+
LocalParticipant,
|
|
2889
|
+
LocalTrackPublication,
|
|
2890
|
+
LogLevel,
|
|
2891
|
+
Participant,
|
|
2892
|
+
ParticipantInfo_State,
|
|
2893
|
+
RemoteAudioTrack,
|
|
2894
|
+
RemoteParticipant,
|
|
2895
|
+
RemoteTrackPublication,
|
|
2896
|
+
Room,
|
|
2897
|
+
TrackPublication,
|
|
2898
|
+
TrackSource,
|
|
2899
|
+
TrackType,
|
|
2900
|
+
createTextMessage,
|
|
2901
|
+
decodeDataPacket,
|
|
2902
|
+
downsample,
|
|
2903
|
+
encodeDataPacket,
|
|
2904
|
+
resample,
|
|
2905
|
+
setLogLevel,
|
|
2906
|
+
upsample
|
|
2907
|
+
});
|
|
2908
|
+
//# sourceMappingURL=index.js.map
|