livekit-client 1.7.1 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +21 -1
- package/dist/livekit-client.esm.mjs +14241 -12994
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts +11 -10
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/connectionHelper/ConnectionCheck.d.ts +1 -1
- package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/Checker.d.ts +1 -1
- package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
- package/dist/src/index.d.ts +7 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/proto/google/protobuf/timestamp.d.ts.map +1 -1
- package/dist/src/proto/livekit_models.d.ts +37 -0
- package/dist/src/proto/livekit_models.d.ts.map +1 -1
- package/dist/src/proto/livekit_rtc.d.ts +347 -75
- package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +12 -3
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/ReconnectPolicy.d.ts +1 -0
- package/dist/src/room/ReconnectPolicy.d.ts.map +1 -1
- package/dist/src/room/RegionUrlProvider.d.ts +14 -0
- package/dist/src/room/RegionUrlProvider.d.ts.map +1 -0
- package/dist/src/room/Room.d.ts +23 -15
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/errors.d.ts +2 -1
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +23 -2
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +14 -2
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +4 -2
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +2 -2
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +4 -3
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrackPublication.d.ts +1 -1
- package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts +1 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +3 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/create.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +5 -0
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +4 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +14 -10
- package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +1 -1
- package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +1 -1
- package/dist/ts4.2/src/index.d.ts +7 -6
- package/dist/ts4.2/src/proto/livekit_models.d.ts +37 -0
- package/dist/ts4.2/src/proto/livekit_rtc.d.ts +380 -84
- package/dist/ts4.2/src/room/RTCEngine.d.ts +12 -3
- package/dist/ts4.2/src/room/ReconnectPolicy.d.ts +1 -0
- package/dist/ts4.2/src/room/RegionUrlProvider.d.ts +14 -0
- package/dist/ts4.2/src/room/Room.d.ts +23 -15
- package/dist/ts4.2/src/room/errors.d.ts +2 -1
- package/dist/ts4.2/src/room/events.d.ts +23 -2
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +14 -2
- package/dist/ts4.2/src/room/participant/Participant.d.ts +4 -2
- package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +2 -2
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +4 -3
- package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -1
- package/dist/ts4.2/src/room/track/RemoteAudioTrack.d.ts +1 -1
- package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +1 -1
- package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -1
- package/dist/ts4.2/src/room/track/Track.d.ts +3 -1
- package/dist/ts4.2/src/room/types.d.ts +5 -0
- package/dist/ts4.2/src/room/utils.d.ts +4 -0
- package/package.json +21 -20
- package/src/api/SignalClient.ts +41 -29
- package/src/connectionHelper/ConnectionCheck.ts +1 -2
- package/src/connectionHelper/checks/Checker.ts +1 -1
- package/src/connectionHelper/checks/reconnect.ts +1 -1
- package/src/index.ts +9 -8
- package/src/proto/google/protobuf/timestamp.ts +15 -6
- package/src/proto/livekit_models.ts +917 -221
- package/src/proto/livekit_rtc.ts +1053 -279
- package/src/room/RTCEngine.ts +171 -47
- package/src/room/ReconnectPolicy.ts +2 -0
- package/src/room/RegionUrlProvider.ts +73 -0
- package/src/room/Room.ts +278 -177
- package/src/room/errors.ts +1 -0
- package/src/room/events.ts +24 -0
- package/src/room/participant/LocalParticipant.ts +30 -7
- package/src/room/participant/Participant.ts +27 -3
- package/src/room/participant/RemoteParticipant.ts +6 -3
- package/src/room/participant/publishUtils.test.ts +1 -1
- package/src/room/participant/publishUtils.ts +1 -1
- package/src/room/track/LocalAudioTrack.ts +14 -7
- package/src/room/track/LocalTrack.ts +23 -9
- package/src/room/track/LocalTrackPublication.ts +1 -1
- package/src/room/track/LocalVideoTrack.ts +15 -9
- package/src/room/track/RemoteAudioTrack.ts +1 -1
- package/src/room/track/RemoteTrackPublication.ts +4 -3
- package/src/room/track/RemoteVideoTrack.test.ts +1 -1
- package/src/room/track/RemoteVideoTrack.ts +8 -7
- package/src/room/track/Track.ts +46 -31
- package/src/room/track/create.ts +2 -2
- package/src/room/types.ts +17 -0
- package/src/room/utils.ts +53 -0
package/src/room/Room.ts
CHANGED
@@ -29,6 +29,9 @@ import {
|
|
29
29
|
StreamStateUpdate,
|
30
30
|
SubscriptionPermissionUpdate,
|
31
31
|
} from '../proto/livekit_rtc';
|
32
|
+
import DeviceManager from './DeviceManager';
|
33
|
+
import RTCEngine from './RTCEngine';
|
34
|
+
import { RegionUrlProvider } from './RegionUrlProvider';
|
32
35
|
import {
|
33
36
|
audioDefaults,
|
34
37
|
publishDefaults,
|
@@ -36,15 +39,12 @@ import {
|
|
36
39
|
roomOptionDefaults,
|
37
40
|
videoDefaults,
|
38
41
|
} from './defaults';
|
39
|
-
import
|
40
|
-
import { ConnectionError, UnsupportedServer } from './errors';
|
42
|
+
import { ConnectionError, ConnectionErrorReason, UnsupportedServer } from './errors';
|
41
43
|
import { EngineEvent, ParticipantEvent, RoomEvent, TrackEvent } from './events';
|
42
44
|
import LocalParticipant from './participant/LocalParticipant';
|
43
45
|
import type Participant from './participant/Participant';
|
44
46
|
import type { ConnectionQuality } from './participant/Participant';
|
45
47
|
import RemoteParticipant from './participant/RemoteParticipant';
|
46
|
-
import RTCEngine from './RTCEngine';
|
47
|
-
import CriticalTimers from './timers';
|
48
48
|
import LocalAudioTrack from './track/LocalAudioTrack';
|
49
49
|
import LocalTrackPublication from './track/LocalTrackPublication';
|
50
50
|
import LocalVideoTrack from './track/LocalVideoTrack';
|
@@ -54,13 +54,14 @@ import { Track } from './track/Track';
|
|
54
54
|
import type { TrackPublication } from './track/TrackPublication';
|
55
55
|
import type { AdaptiveStreamSettings } from './track/types';
|
56
56
|
import { getNewAudioContext } from './track/utils';
|
57
|
-
import type { SimulationOptions } from './types';
|
57
|
+
import type { SimulationOptions, SimulationScenario } from './types';
|
58
58
|
import {
|
59
|
-
createDummyVideoStreamTrack,
|
60
59
|
Future,
|
60
|
+
Mutex,
|
61
|
+
createDummyVideoStreamTrack,
|
61
62
|
getEmptyAudioStreamTrack,
|
63
|
+
isCloud,
|
62
64
|
isWeb,
|
63
|
-
Mutex,
|
64
65
|
supportsSetSinkId,
|
65
66
|
unpackStreamId,
|
66
67
|
} from './utils';
|
@@ -98,23 +99,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
98
99
|
/** @internal */
|
99
100
|
engine!: RTCEngine;
|
100
101
|
|
101
|
-
// available after connected
|
102
|
-
/** server assigned unique room id */
|
103
|
-
sid: string = '';
|
104
|
-
|
105
|
-
/** user assigned name, derived from JWT token */
|
106
|
-
name: string = '';
|
107
|
-
|
108
102
|
/** the current participant */
|
109
103
|
localParticipant: LocalParticipant;
|
110
104
|
|
111
|
-
/** room metadata */
|
112
|
-
metadata: string | undefined = undefined;
|
113
|
-
|
114
105
|
/** options of room */
|
115
106
|
options: InternalRoomOptions;
|
116
107
|
|
117
|
-
private
|
108
|
+
private roomInfo?: RoomModel;
|
118
109
|
|
119
110
|
private identityToSid: Map<string, string>;
|
120
111
|
|
@@ -164,6 +155,36 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
164
155
|
this.localParticipant = new LocalParticipant('', '', this.engine, this.options);
|
165
156
|
}
|
166
157
|
|
158
|
+
/**
|
159
|
+
* if the current room has a participant with `recorder: true` in its JWT grant
|
160
|
+
**/
|
161
|
+
get isRecording(): boolean {
|
162
|
+
return this.roomInfo?.activeRecording ?? false;
|
163
|
+
}
|
164
|
+
|
165
|
+
/** server assigned unique room id */
|
166
|
+
get sid(): string {
|
167
|
+
return this.roomInfo?.sid ?? '';
|
168
|
+
}
|
169
|
+
|
170
|
+
/** user assigned name, derived from JWT token */
|
171
|
+
get name(): string {
|
172
|
+
return this.roomInfo?.name ?? '';
|
173
|
+
}
|
174
|
+
|
175
|
+
/** room metadata */
|
176
|
+
get metadata(): string | undefined {
|
177
|
+
return this.roomInfo?.metadata;
|
178
|
+
}
|
179
|
+
|
180
|
+
get numParticipants(): number {
|
181
|
+
return this.roomInfo?.numParticipants ?? 0;
|
182
|
+
}
|
183
|
+
|
184
|
+
get numPublishers(): number {
|
185
|
+
return this.roomInfo?.numPublishers ?? 0;
|
186
|
+
}
|
187
|
+
|
167
188
|
private maybeCreateEngine() {
|
168
189
|
if (this.engine) {
|
169
190
|
return;
|
@@ -206,7 +227,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
206
227
|
}
|
207
228
|
})
|
208
229
|
.on(EngineEvent.Restarting, this.handleRestarting)
|
209
|
-
.on(EngineEvent.
|
230
|
+
.on(EngineEvent.SignalRestarted, this.handleSignalRestarted)
|
231
|
+
.on(EngineEvent.DCBufferStatusChanged, (status, kind) => {
|
232
|
+
this.emit(RoomEvent.DCBufferStatusChanged, status, kind);
|
233
|
+
});
|
210
234
|
|
211
235
|
if (this.localParticipant) {
|
212
236
|
this.localParticipant.setupEngine(this.engine);
|
@@ -258,155 +282,201 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
258
282
|
|
259
283
|
this.setAndEmitConnectionState(ConnectionState.Connecting);
|
260
284
|
|
261
|
-
const
|
262
|
-
|
263
|
-
|
285
|
+
const urlProvider = new RegionUrlProvider(url, token);
|
286
|
+
|
287
|
+
const connectFn = async (
|
288
|
+
resolve: () => void,
|
289
|
+
reject: (reason: any) => void,
|
290
|
+
regionUrl?: string,
|
291
|
+
) => {
|
292
|
+
if (this.abortController) {
|
293
|
+
this.abortController.abort();
|
264
294
|
}
|
295
|
+
this.abortController = new AbortController();
|
296
|
+
|
265
297
|
// at this point the intention to connect has been signalled so we can allow cancelling of the connection via disconnect() again
|
266
|
-
unlockDisconnect();
|
298
|
+
unlockDisconnect?.();
|
267
299
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
}
|
273
|
-
|
274
|
-
|
300
|
+
try {
|
301
|
+
await this.attemptConnection(regionUrl ?? url, token, opts, this.abortController);
|
302
|
+
this.abortController = undefined;
|
303
|
+
resolve();
|
304
|
+
} catch (e) {
|
305
|
+
if (
|
306
|
+
isCloud(new URL(url)) &&
|
307
|
+
e instanceof ConnectionError &&
|
308
|
+
e.reason !== ConnectionErrorReason.Cancelled
|
309
|
+
) {
|
310
|
+
let nextUrl: string | null = null;
|
311
|
+
try {
|
312
|
+
nextUrl = await urlProvider.getNextBestRegionUrl(this.abortController?.signal);
|
313
|
+
} catch (error) {
|
314
|
+
if (
|
315
|
+
error instanceof ConnectionError &&
|
316
|
+
(error.status === 401 || error.reason === ConnectionErrorReason.Cancelled)
|
317
|
+
) {
|
318
|
+
reject(error);
|
319
|
+
return;
|
320
|
+
}
|
321
|
+
}
|
322
|
+
if (nextUrl) {
|
323
|
+
log.debug('initial connection failed, retrying with another region');
|
324
|
+
await connectFn(resolve, reject, nextUrl);
|
325
|
+
} else {
|
326
|
+
reject(e);
|
327
|
+
}
|
328
|
+
} else {
|
329
|
+
reject(e);
|
330
|
+
}
|
275
331
|
}
|
332
|
+
};
|
333
|
+
this.connectFuture = new Future(connectFn, () => {
|
334
|
+
this.clearConnectionFutures();
|
335
|
+
});
|
276
336
|
|
277
|
-
|
337
|
+
return this.connectFuture.promise;
|
338
|
+
};
|
278
339
|
|
279
|
-
|
340
|
+
private connectSignal = async (
|
341
|
+
url: string,
|
342
|
+
token: string,
|
343
|
+
engine: RTCEngine,
|
344
|
+
connectOptions: InternalRoomConnectOptions,
|
345
|
+
roomOptions: InternalRoomOptions,
|
346
|
+
abortController: AbortController,
|
347
|
+
): Promise<JoinResponse> => {
|
348
|
+
const joinResponse = await engine.join(
|
349
|
+
url,
|
350
|
+
token,
|
351
|
+
{
|
352
|
+
autoSubscribe: connectOptions.autoSubscribe,
|
353
|
+
publishOnly: connectOptions.publishOnly,
|
354
|
+
adaptiveStream:
|
355
|
+
typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
|
356
|
+
maxRetries: connectOptions.maxRetries,
|
357
|
+
},
|
358
|
+
abortController.signal,
|
359
|
+
);
|
280
360
|
|
281
|
-
|
282
|
-
|
283
|
-
}
|
284
|
-
|
285
|
-
this.engine.peerConnectionTimeout = this.connOptions.peerConnectionTimeout;
|
286
|
-
}
|
361
|
+
let serverInfo: Partial<ServerInfo> | undefined = joinResponse.serverInfo;
|
362
|
+
if (!serverInfo) {
|
363
|
+
serverInfo = { version: joinResponse.serverVersion, region: joinResponse.serverRegion };
|
364
|
+
}
|
287
365
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
autoSubscribe: this.connOptions.autoSubscribe,
|
294
|
-
publishOnly: this.connOptions.publishOnly,
|
295
|
-
adaptiveStream:
|
296
|
-
typeof this.options.adaptiveStream === 'object' ? true : this.options.adaptiveStream,
|
297
|
-
maxRetries: this.connOptions.maxRetries,
|
298
|
-
},
|
299
|
-
this.abortController.signal,
|
300
|
-
);
|
366
|
+
log.debug(
|
367
|
+
`connected to Livekit Server ${Object.entries(serverInfo)
|
368
|
+
.map(([key, value]) => `${key}: ${value}`)
|
369
|
+
.join(', ')}`,
|
370
|
+
);
|
301
371
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
}
|
372
|
+
if (!joinResponse.serverVersion) {
|
373
|
+
throw new UnsupportedServer('unknown server version');
|
374
|
+
}
|
306
375
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
376
|
+
if (joinResponse.serverVersion === '0.15.1' && this.options.dynacast) {
|
377
|
+
log.debug('disabling dynacast due to server version');
|
378
|
+
// dynacast has a bug in 0.15.1, so we cannot use it then
|
379
|
+
roomOptions.dynacast = false;
|
380
|
+
}
|
312
381
|
|
313
|
-
|
314
|
-
|
315
|
-
}
|
382
|
+
return joinResponse;
|
383
|
+
};
|
316
384
|
|
317
|
-
|
318
|
-
|
319
|
-
// dynacast has a bug in 0.15.1, so we cannot use it then
|
320
|
-
this.options.dynacast = false;
|
321
|
-
}
|
385
|
+
private applyJoinResponse = (joinResponse: JoinResponse) => {
|
386
|
+
const pi = joinResponse.participant!;
|
322
387
|
|
323
|
-
|
388
|
+
this.localParticipant.sid = pi.sid;
|
389
|
+
this.localParticipant.identity = pi.identity;
|
324
390
|
|
325
|
-
|
326
|
-
|
391
|
+
// populate remote participants, these should not trigger new events
|
392
|
+
this.handleParticipantUpdates([pi, ...joinResponse.otherParticipants]);
|
327
393
|
|
328
|
-
|
329
|
-
|
330
|
-
|
394
|
+
if (joinResponse.room) {
|
395
|
+
this.handleRoomUpdate(joinResponse.room);
|
396
|
+
}
|
397
|
+
};
|
331
398
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
399
|
+
private attemptConnection = async (
|
400
|
+
url: string,
|
401
|
+
token: string,
|
402
|
+
opts: RoomConnectOptions | undefined,
|
403
|
+
abortController: AbortController,
|
404
|
+
) => {
|
405
|
+
if (this.state === ConnectionState.Reconnecting) {
|
406
|
+
log.info('Reconnection attempt replaced by new connection attempt');
|
407
|
+
// make sure we close and recreate the existing engine in order to get rid of any potentially ongoing reconnection attempts
|
408
|
+
this.recreateEngine();
|
409
|
+
} else {
|
410
|
+
// create engine if previously disconnected
|
411
|
+
this.maybeCreateEngine();
|
412
|
+
}
|
346
413
|
|
347
|
-
|
348
|
-
this.sid = joinResponse.room!.sid;
|
349
|
-
this.metadata = joinResponse.room!.metadata;
|
350
|
-
if (this._isRecording !== joinResponse.room!.activeRecording) {
|
351
|
-
this._isRecording = joinResponse.room!.activeRecording;
|
352
|
-
this.emit(RoomEvent.RecordingStatusChanged, joinResponse.room!.activeRecording);
|
353
|
-
}
|
354
|
-
this.emit(RoomEvent.SignalConnected);
|
355
|
-
} catch (err) {
|
356
|
-
this.recreateEngine();
|
357
|
-
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
358
|
-
const resultingError = new ConnectionError(`could not establish signal connection`);
|
359
|
-
if (err instanceof Error) {
|
360
|
-
resultingError.message = `${resultingError.message}: ${err.message}`;
|
361
|
-
}
|
362
|
-
if (err instanceof ConnectionError) {
|
363
|
-
resultingError.reason = err.reason;
|
364
|
-
resultingError.status = err.status;
|
365
|
-
}
|
366
|
-
log.debug(`error trying to establish signal connection`, { error: err });
|
367
|
-
reject(resultingError);
|
368
|
-
return;
|
369
|
-
}
|
414
|
+
this.acquireAudioContext();
|
370
415
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
416
|
+
this.connOptions = { ...roomConnectOptionDefaults, ...opts } as InternalRoomConnectOptions;
|
417
|
+
|
418
|
+
if (this.connOptions.rtcConfig) {
|
419
|
+
this.engine.rtcConfig = this.connOptions.rtcConfig;
|
420
|
+
}
|
421
|
+
if (this.connOptions.peerConnectionTimeout) {
|
422
|
+
this.engine.peerConnectionTimeout = this.connOptions.peerConnectionTimeout;
|
423
|
+
}
|
424
|
+
|
425
|
+
try {
|
426
|
+
const joinResponse = await this.connectSignal(
|
427
|
+
url,
|
428
|
+
token,
|
429
|
+
this.engine,
|
430
|
+
this.connOptions,
|
431
|
+
this.options,
|
432
|
+
abortController,
|
433
|
+
);
|
434
|
+
|
435
|
+
this.applyJoinResponse(joinResponse);
|
436
|
+
// forward metadata changed for the local participant
|
437
|
+
this.setupLocalParticipantEvents();
|
438
|
+
this.emit(RoomEvent.SignalConnected);
|
439
|
+
} catch (err) {
|
440
|
+
this.recreateEngine();
|
441
|
+
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
442
|
+
const resultingError = new ConnectionError(`could not establish signal connection`);
|
443
|
+
if (err instanceof Error) {
|
444
|
+
resultingError.message = `${resultingError.message}: ${err.message}`;
|
387
445
|
}
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
// capturing both 'pagehide' and 'beforeunload' to capture broadest set of browser behaviors
|
396
|
-
window.addEventListener('pagehide', this.onPageLeave);
|
397
|
-
window.addEventListener('beforeunload', this.onPageLeave);
|
398
|
-
navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange);
|
399
|
-
}
|
400
|
-
this.setAndEmitConnectionState(ConnectionState.Connected);
|
401
|
-
this.emit(RoomEvent.Connected);
|
402
|
-
resolve();
|
403
|
-
});
|
404
|
-
};
|
405
|
-
this.connectFuture = new Future(connectFn, () => {
|
406
|
-
this.clearConnectionFutures();
|
407
|
-
});
|
446
|
+
if (err instanceof ConnectionError) {
|
447
|
+
resultingError.reason = err.reason;
|
448
|
+
resultingError.status = err.status;
|
449
|
+
}
|
450
|
+
log.debug(`error trying to establish signal connection`, { error: err });
|
451
|
+
throw resultingError;
|
452
|
+
}
|
408
453
|
|
409
|
-
|
454
|
+
if (abortController.signal.aborted) {
|
455
|
+
this.recreateEngine();
|
456
|
+
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
457
|
+
throw new ConnectionError(`Connection attempt aborted`);
|
458
|
+
}
|
459
|
+
|
460
|
+
try {
|
461
|
+
await this.engine.waitForPCInitialConnection(
|
462
|
+
this.connOptions.peerConnectionTimeout,
|
463
|
+
abortController,
|
464
|
+
);
|
465
|
+
} catch (e) {
|
466
|
+
this.recreateEngine();
|
467
|
+
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
468
|
+
throw e;
|
469
|
+
}
|
470
|
+
|
471
|
+
// also hook unload event
|
472
|
+
if (isWeb() && this.options.disconnectOnPageLeave) {
|
473
|
+
// capturing both 'pagehide' and 'beforeunload' to capture broadest set of browser behaviors
|
474
|
+
window.addEventListener('pagehide', this.onPageLeave);
|
475
|
+
window.addEventListener('beforeunload', this.onPageLeave);
|
476
|
+
navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange);
|
477
|
+
}
|
478
|
+
this.setAndEmitConnectionState(ConnectionState.Connected);
|
479
|
+
this.emit(RoomEvent.Connected);
|
410
480
|
};
|
411
481
|
|
412
482
|
/**
|
@@ -466,17 +536,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
466
536
|
this.connectFuture = undefined;
|
467
537
|
}
|
468
538
|
|
469
|
-
/**
|
470
|
-
* if the current room has a participant with `recorder: true` in its JWT grant
|
471
|
-
**/
|
472
|
-
get isRecording() {
|
473
|
-
return this._isRecording;
|
474
|
-
}
|
475
|
-
|
476
539
|
/**
|
477
540
|
* @internal for testing
|
478
541
|
*/
|
479
|
-
async simulateScenario(scenario:
|
542
|
+
async simulateScenario(scenario: SimulationScenario) {
|
480
543
|
let postAction = () => {};
|
481
544
|
let req: SimulateScenario | undefined;
|
482
545
|
switch (scenario) {
|
@@ -525,6 +588,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
525
588
|
this.engine.client.onClose('simulate resume-reconnect');
|
526
589
|
}
|
527
590
|
break;
|
591
|
+
case 'full-reconnect':
|
592
|
+
this.engine.fullReconnectOnNext = true;
|
593
|
+
await this.engine.client.close();
|
594
|
+
if (this.engine.client.onClose) {
|
595
|
+
this.engine.client.onClose('simulate full-reconnect');
|
596
|
+
}
|
597
|
+
break;
|
528
598
|
case 'force-tcp':
|
529
599
|
case 'force-tls':
|
530
600
|
req = SimulateScenario.fromPartial({
|
@@ -666,6 +736,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
666
736
|
private setupLocalParticipantEvents() {
|
667
737
|
this.localParticipant
|
668
738
|
.on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
|
739
|
+
.on(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged)
|
669
740
|
.on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
|
670
741
|
.on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
|
671
742
|
.on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
|
@@ -757,20 +828,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
757
828
|
}
|
758
829
|
};
|
759
830
|
|
760
|
-
private
|
761
|
-
log.debug(`reconnected to server`, {
|
831
|
+
private handleSignalRestarted = async (joinResponse: JoinResponse) => {
|
832
|
+
log.debug(`signal reconnected to server`, {
|
762
833
|
region: joinResponse.serverRegion,
|
763
834
|
});
|
764
835
|
|
765
|
-
|
766
|
-
// rehydrate participants
|
767
|
-
if (joinResponse.participant) {
|
768
|
-
// with a restart, the sid will have changed, we'll map our understanding to it
|
769
|
-
this.localParticipant.sid = joinResponse.participant.sid;
|
770
|
-
this.handleParticipantUpdates([joinResponse.participant]);
|
771
|
-
}
|
772
|
-
this.handleParticipantUpdates(joinResponse.otherParticipants);
|
836
|
+
this.applyJoinResponse(joinResponse);
|
773
837
|
|
838
|
+
try {
|
774
839
|
// unpublish & republish tracks
|
775
840
|
const localPubs: LocalTrackPublication[] = [];
|
776
841
|
this.localParticipant.tracks.forEach((pub) => {
|
@@ -804,10 +869,24 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
804
869
|
);
|
805
870
|
} catch (error) {
|
806
871
|
log.error('error trying to re-publish tracks after reconnection', { error });
|
807
|
-
} finally {
|
808
|
-
this.setAndEmitConnectionState(ConnectionState.Connected);
|
809
|
-
this.emit(RoomEvent.Reconnected);
|
810
872
|
}
|
873
|
+
|
874
|
+
try {
|
875
|
+
await this.engine.waitForRestarted();
|
876
|
+
log.debug(`fully reconnected to server`, {
|
877
|
+
region: joinResponse.serverRegion,
|
878
|
+
});
|
879
|
+
} catch {
|
880
|
+
// reconnection failed, handleDisconnect is being invoked already, just return here
|
881
|
+
return;
|
882
|
+
}
|
883
|
+
this.setAndEmitConnectionState(ConnectionState.Connected);
|
884
|
+
this.emit(RoomEvent.Reconnected);
|
885
|
+
|
886
|
+
// emit participant connected events after connection has been re-established
|
887
|
+
this.participants.forEach((participant) => {
|
888
|
+
this.emit(RoomEvent.ParticipantConnected, participant);
|
889
|
+
});
|
811
890
|
};
|
812
891
|
|
813
892
|
private handleDisconnect(shouldStopTracks = true, reason?: DisconnectReason) {
|
@@ -833,6 +912,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
833
912
|
|
834
913
|
this.localParticipant
|
835
914
|
.off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
|
915
|
+
.off(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged)
|
836
916
|
.off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
|
837
917
|
.off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
|
838
918
|
.off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
|
@@ -1038,14 +1118,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1038
1118
|
this.emit(RoomEvent.MediaDevicesChanged);
|
1039
1119
|
};
|
1040
1120
|
|
1041
|
-
private handleRoomUpdate = (
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1121
|
+
private handleRoomUpdate = (room: RoomModel) => {
|
1122
|
+
const oldRoom = this.roomInfo;
|
1123
|
+
this.roomInfo = room;
|
1124
|
+
if (oldRoom && oldRoom.metadata !== room.metadata) {
|
1125
|
+
this.emitWhenConnected(RoomEvent.RoomMetadataChanged, room.metadata);
|
1045
1126
|
}
|
1046
|
-
if (
|
1047
|
-
this.
|
1048
|
-
this.emitWhenConnected(RoomEvent.RoomMetadataChanged, r.metadata);
|
1127
|
+
if (oldRoom?.activeRecording !== room.activeRecording) {
|
1128
|
+
this.emitWhenConnected(RoomEvent.RecordingStatusChanged, room.activeRecording);
|
1049
1129
|
}
|
1050
1130
|
};
|
1051
1131
|
|
@@ -1154,6 +1234,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1154
1234
|
.on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
|
1155
1235
|
this.emitWhenConnected(RoomEvent.ParticipantMetadataChanged, metadata, participant);
|
1156
1236
|
})
|
1237
|
+
.on(ParticipantEvent.ParticipantNameChanged, (name) => {
|
1238
|
+
this.emitWhenConnected(RoomEvent.ParticipantNameChanged, name, participant);
|
1239
|
+
})
|
1157
1240
|
.on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
|
1158
1241
|
this.emitWhenConnected(RoomEvent.ConnectionQualityChanged, quality, participant);
|
1159
1242
|
})
|
@@ -1270,6 +1353,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1270
1353
|
this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
|
1271
1354
|
};
|
1272
1355
|
|
1356
|
+
private onLocalParticipantNameChanged = (name: string) => {
|
1357
|
+
this.emit(RoomEvent.ParticipantNameChanged, name, this.localParticipant);
|
1358
|
+
};
|
1359
|
+
|
1273
1360
|
private onLocalTrackMuted = (pub: TrackPublication) => {
|
1274
1361
|
this.emit(RoomEvent.TrackMuted, pub, this.localParticipant);
|
1275
1362
|
};
|
@@ -1324,7 +1411,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1324
1411
|
...options.participants,
|
1325
1412
|
};
|
1326
1413
|
this.handleDisconnect();
|
1327
|
-
this.
|
1414
|
+
this.roomInfo = {
|
1415
|
+
sid: 'RM_SIMULATED',
|
1416
|
+
name: 'simulated-room',
|
1417
|
+
emptyTimeout: 0,
|
1418
|
+
maxParticipants: 0,
|
1419
|
+
creationTime: new Date().getTime(),
|
1420
|
+
metadata: '',
|
1421
|
+
numParticipants: 1,
|
1422
|
+
numPublishers: 1,
|
1423
|
+
turnPassword: '',
|
1424
|
+
enabledCodecs: [],
|
1425
|
+
activeRecording: false,
|
1426
|
+
};
|
1328
1427
|
|
1329
1428
|
this.localParticipant.updateInfo(
|
1330
1429
|
ParticipantInfo.fromPartial({
|
@@ -1469,6 +1568,7 @@ export type RoomEventCallbacks = {
|
|
1469
1568
|
metadata: string | undefined,
|
1470
1569
|
participant: RemoteParticipant | LocalParticipant,
|
1471
1570
|
) => void;
|
1571
|
+
participantNameChanged: (name: string, participant: RemoteParticipant | LocalParticipant) => void;
|
1472
1572
|
participantPermissionsChanged: (
|
1473
1573
|
prevPermissions: ParticipantPermission | undefined,
|
1474
1574
|
participant: RemoteParticipant | LocalParticipant,
|
@@ -1501,4 +1601,5 @@ export type RoomEventCallbacks = {
|
|
1501
1601
|
audioPlaybackChanged: (playing: boolean) => void;
|
1502
1602
|
signalConnected: () => void;
|
1503
1603
|
recordingStatusChanged: (recording: boolean) => void;
|
1604
|
+
dcBufferStatusChanged: (isLow: boolean, kind: DataPacket_Kind) => void;
|
1504
1605
|
};
|