livekit-client 1.2.2 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.esm.mjs +1990 -905
- 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/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/options.d.ts +5 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/proto/google/protobuf/timestamp.d.ts +1 -1
- package/dist/src/proto/google/protobuf/timestamp.d.ts.map +1 -1
- package/dist/src/proto/livekit_models.d.ts +34 -34
- package/dist/src/proto/livekit_models.d.ts.map +1 -1
- package/dist/src/proto/livekit_rtc.d.ts +124 -124
- package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
- package/dist/src/room/DefaultReconnectPolicy.d.ts +8 -0
- package/dist/src/room/DefaultReconnectPolicy.d.ts.map +1 -0
- package/dist/src/room/DeviceManager.d.ts +1 -0
- package/dist/src/room/DeviceManager.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +5 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +7 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/ReconnectPolicy.d.ts +23 -0
- package/dist/src/room/ReconnectPolicy.d.ts.map +1 -0
- package/dist/src/room/Room.d.ts +12 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +2 -2
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +0 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts +0 -2
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/create.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +1 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/package.json +37 -33
- package/src/index.ts +2 -0
- package/src/options.ts +6 -0
- package/src/room/DefaultReconnectPolicy.ts +35 -0
- package/src/room/DeviceManager.ts +23 -1
- package/src/room/PCTransport.ts +91 -17
- package/src/room/RTCEngine.ts +105 -33
- package/src/room/ReconnectPolicy.ts +25 -0
- package/src/room/Room.ts +190 -167
- package/src/room/events.ts +2 -2
- package/src/room/participant/LocalParticipant.ts +38 -14
- package/src/room/participant/RemoteParticipant.ts +14 -0
- package/src/room/track/LocalAudioTrack.ts +0 -2
- package/src/room/track/LocalVideoTrack.ts +3 -8
- package/src/room/track/RemoteTrackPublication.ts +1 -1
- package/src/room/track/RemoteVideoTrack.ts +0 -3
- package/src/room/track/create.ts +16 -1
- package/src/room/utils.ts +7 -5
package/src/room/Room.ts
CHANGED
@@ -99,8 +99,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
99
99
|
/** used for aborting pending connections to a LiveKit server */
|
100
100
|
private abortController?: AbortController;
|
101
101
|
|
102
|
+
/** future holding client initiated connection attempt */
|
102
103
|
private connectFuture?: Future<void>;
|
103
104
|
|
105
|
+
/** future holding sdk initiated reconnection attempt */
|
106
|
+
private reconnectFuture?: Future<void>;
|
107
|
+
|
104
108
|
/**
|
105
109
|
* Creates a new Room, the primary construct for a LiveKit session.
|
106
110
|
* @param options
|
@@ -134,9 +138,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
134
138
|
return;
|
135
139
|
}
|
136
140
|
|
137
|
-
this.engine = new RTCEngine();
|
141
|
+
this.engine = new RTCEngine(this.options);
|
138
142
|
|
139
|
-
this.engine.client.signalLatency = this.options.expSignalLatency;
|
140
143
|
this.engine.client.onParticipantUpdate = this.handleParticipantUpdates;
|
141
144
|
this.engine.client.onRoomUpdate = this.handleRoomUpdate;
|
142
145
|
this.engine.client.onSpeakersChanged = this.handleSpeakersChanged;
|
@@ -157,12 +160,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
157
160
|
.on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
|
158
161
|
.on(EngineEvent.DataPacketReceived, this.handleDataPacket)
|
159
162
|
.on(EngineEvent.Resuming, () => {
|
163
|
+
if (!this.reconnectFuture) {
|
164
|
+
this.reconnectFuture = new Future();
|
165
|
+
}
|
160
166
|
if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
|
161
167
|
this.emit(RoomEvent.Reconnecting);
|
162
168
|
}
|
163
169
|
})
|
164
170
|
.on(EngineEvent.Resumed, () => {
|
165
171
|
this.setAndEmitConnectionState(ConnectionState.Connected);
|
172
|
+
this.reconnectFuture?.resolve();
|
173
|
+
this.reconnectFuture = undefined;
|
166
174
|
this.emit(RoomEvent.Reconnected);
|
167
175
|
this.updateSubscriptions();
|
168
176
|
})
|
@@ -194,165 +202,145 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
194
202
|
return DeviceManager.getInstance().getDevices(kind, requestPermissions);
|
195
203
|
}
|
196
204
|
|
197
|
-
connect =
|
205
|
+
connect = (url: string, token: string, opts?: RoomConnectOptions): Promise<void> => {
|
198
206
|
if (this.state === ConnectionState.Connected) {
|
199
207
|
// when the state is reconnecting or connected, this function returns immediately
|
200
208
|
log.warn(`already connected to room ${this.name}`);
|
201
|
-
return;
|
209
|
+
return Promise.resolve();
|
202
210
|
}
|
203
211
|
|
204
212
|
if (this.connectFuture) {
|
205
213
|
return this.connectFuture.promise;
|
214
|
+
} else if (this.reconnectFuture) {
|
215
|
+
this.connectFuture = this.reconnectFuture;
|
216
|
+
return this.connectFuture.promise;
|
206
217
|
}
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
// recreate engine if previously disconnected
|
214
|
-
this.createEngine();
|
215
|
-
|
216
|
-
this.acquireAudioContext();
|
217
|
-
|
218
|
-
if (opts?.rtcConfig) {
|
219
|
-
this.engine.rtcConfig = opts.rtcConfig;
|
220
|
-
}
|
221
|
-
|
222
|
-
this.connOptions = opts;
|
218
|
+
const connectPromise = new Promise<void>(async (resolve, reject) => {
|
219
|
+
this.setAndEmitConnectionState(ConnectionState.Connecting);
|
220
|
+
if (!this.abortController || this.abortController.signal.aborted) {
|
221
|
+
this.abortController = new AbortController();
|
222
|
+
}
|
223
223
|
|
224
|
-
|
225
|
-
|
226
|
-
url,
|
227
|
-
token,
|
228
|
-
{
|
229
|
-
autoSubscribe: opts?.autoSubscribe,
|
230
|
-
publishOnly: opts?.publishOnly,
|
231
|
-
adaptiveStream:
|
232
|
-
typeof this.options?.adaptiveStream === 'object' ? true : this.options?.adaptiveStream,
|
233
|
-
},
|
234
|
-
this.abortController.signal,
|
235
|
-
);
|
236
|
-
log.debug(
|
237
|
-
`connected to Livekit Server version: ${joinResponse.serverVersion}, region: ${joinResponse.serverRegion}`,
|
238
|
-
);
|
224
|
+
// recreate engine if previously disconnected
|
225
|
+
this.createEngine();
|
239
226
|
|
240
|
-
|
241
|
-
throw new UnsupportedServer('unknown server version');
|
242
|
-
}
|
227
|
+
this.acquireAudioContext();
|
243
228
|
|
244
|
-
if (
|
245
|
-
|
246
|
-
// dynacast has a bug in 0.15.1, so we cannot use it then
|
247
|
-
this.options.dynacast = false;
|
229
|
+
if (opts?.rtcConfig) {
|
230
|
+
this.engine.rtcConfig = opts.rtcConfig;
|
248
231
|
}
|
249
232
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
})
|
264
|
-
.on(ParticipantEvent.TrackUnmuted, (pub: TrackPublication) => {
|
265
|
-
this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
|
266
|
-
})
|
267
|
-
.on(ParticipantEvent.LocalTrackPublished, (pub: LocalTrackPublication) => {
|
268
|
-
this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
|
269
|
-
})
|
270
|
-
.on(ParticipantEvent.LocalTrackUnpublished, (pub: LocalTrackPublication) => {
|
271
|
-
this.emit(RoomEvent.LocalTrackUnpublished, pub, this.localParticipant);
|
272
|
-
})
|
273
|
-
.on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
|
274
|
-
this.emit(RoomEvent.ConnectionQualityChanged, quality, this.localParticipant);
|
275
|
-
})
|
276
|
-
.on(ParticipantEvent.MediaDevicesError, (e: Error) => {
|
277
|
-
this.emit(RoomEvent.MediaDevicesError, e);
|
278
|
-
})
|
279
|
-
.on(
|
280
|
-
ParticipantEvent.ParticipantPermissionsChanged,
|
281
|
-
(prevPermissions: ParticipantPermission) => {
|
282
|
-
this.emit(
|
283
|
-
RoomEvent.ParticipantPermissionsChanged,
|
284
|
-
prevPermissions,
|
285
|
-
this.localParticipant,
|
286
|
-
);
|
233
|
+
this.connOptions = opts;
|
234
|
+
|
235
|
+
try {
|
236
|
+
const joinResponse = await this.engine.join(
|
237
|
+
url,
|
238
|
+
token,
|
239
|
+
{
|
240
|
+
autoSubscribe: opts?.autoSubscribe,
|
241
|
+
publishOnly: opts?.publishOnly,
|
242
|
+
adaptiveStream:
|
243
|
+
typeof this.options?.adaptiveStream === 'object'
|
244
|
+
? true
|
245
|
+
: this.options?.adaptiveStream,
|
287
246
|
},
|
247
|
+
this.abortController.signal,
|
248
|
+
);
|
249
|
+
log.debug(
|
250
|
+
`connected to Livekit Server version: ${joinResponse.serverVersion}, region: ${joinResponse.serverRegion}`,
|
288
251
|
);
|
289
252
|
|
290
|
-
|
291
|
-
|
292
|
-
if (
|
293
|
-
info.sid !== this.localParticipant.sid &&
|
294
|
-
info.identity !== this.localParticipant.identity
|
295
|
-
) {
|
296
|
-
this.getOrCreateParticipant(info.sid, info);
|
297
|
-
} else {
|
298
|
-
log.warn('received info to create local participant as remote participant', {
|
299
|
-
info,
|
300
|
-
localParticipant: this.localParticipant,
|
301
|
-
});
|
253
|
+
if (!joinResponse.serverVersion) {
|
254
|
+
throw new UnsupportedServer('unknown server version');
|
302
255
|
}
|
303
|
-
});
|
304
256
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
this.recreateEngine();
|
311
|
-
this.setAndEmitConnectionState(
|
312
|
-
ConnectionState.Disconnected,
|
313
|
-
new ConnectionError('could not establish signal connection'),
|
314
|
-
);
|
315
|
-
throw err;
|
316
|
-
}
|
257
|
+
if (joinResponse.serverVersion === '0.15.1' && this.options.dynacast) {
|
258
|
+
log.debug('disabling dynacast due to server version');
|
259
|
+
// dynacast has a bug in 0.15.1, so we cannot use it then
|
260
|
+
this.options.dynacast = false;
|
261
|
+
}
|
317
262
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
263
|
+
const pi = joinResponse.participant!;
|
264
|
+
|
265
|
+
this.localParticipant.sid = pi.sid;
|
266
|
+
this.localParticipant.identity = pi.identity;
|
267
|
+
|
268
|
+
this.localParticipant.updateInfo(pi);
|
269
|
+
// forward metadata changed for the local participant
|
270
|
+
this.localParticipant
|
271
|
+
.on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
|
272
|
+
.on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
|
273
|
+
.on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
|
274
|
+
.on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
|
275
|
+
.on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished)
|
276
|
+
.on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged)
|
277
|
+
.on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError)
|
278
|
+
.on(
|
279
|
+
ParticipantEvent.ParticipantPermissionsChanged,
|
280
|
+
this.onLocalParticipantPermissionsChanged,
|
281
|
+
);
|
282
|
+
|
283
|
+
// populate remote participants, these should not trigger new events
|
284
|
+
joinResponse.otherParticipants.forEach((info) => {
|
285
|
+
if (
|
286
|
+
info.sid !== this.localParticipant.sid &&
|
287
|
+
info.identity !== this.localParticipant.identity
|
288
|
+
) {
|
289
|
+
this.getOrCreateParticipant(info.sid, info);
|
290
|
+
} else {
|
291
|
+
log.warn('received info to create local participant as remote participant', {
|
292
|
+
info,
|
293
|
+
localParticipant: this.localParticipant,
|
294
|
+
});
|
295
|
+
}
|
296
|
+
});
|
297
|
+
|
298
|
+
this.name = joinResponse.room!.name;
|
299
|
+
this.sid = joinResponse.room!.sid;
|
300
|
+
this.metadata = joinResponse.room!.metadata;
|
301
|
+
this.emit(RoomEvent.SignalConnected);
|
302
|
+
} catch (err) {
|
303
|
+
this.recreateEngine();
|
304
|
+
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
305
|
+
reject(new ConnectionError('could not establish signal connection'));
|
306
|
+
}
|
307
|
+
|
308
|
+
// don't return until ICE connected
|
309
|
+
const connectTimeout = setTimeout(() => {
|
310
|
+
// timeout
|
311
|
+
this.recreateEngine();
|
312
|
+
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
313
|
+
reject(new ConnectionError('could not connect PeerConnection after timeout'));
|
314
|
+
}, maxICEConnectTimeout);
|
315
|
+
const abortHandler = () => {
|
316
|
+
log.warn('closing engine');
|
317
|
+
clearTimeout(connectTimeout);
|
318
|
+
this.recreateEngine();
|
319
|
+
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
320
|
+
reject(new ConnectionError('room connection has been cancelled'));
|
321
|
+
};
|
322
|
+
if (this.abortController?.signal.aborted) {
|
323
|
+
abortHandler();
|
348
324
|
}
|
349
|
-
this.
|
325
|
+
this.abortController?.signal.addEventListener('abort', abortHandler);
|
326
|
+
|
327
|
+
this.engine.once(EngineEvent.Connected, () => {
|
328
|
+
clearTimeout(connectTimeout);
|
329
|
+
this.abortController?.signal.removeEventListener('abort', abortHandler);
|
330
|
+
// also hook unload event
|
331
|
+
if (isWeb()) {
|
332
|
+
window.addEventListener('beforeunload', this.onBeforeUnload);
|
333
|
+
navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange);
|
334
|
+
}
|
335
|
+
this.setAndEmitConnectionState(ConnectionState.Connected);
|
336
|
+
resolve();
|
337
|
+
});
|
350
338
|
});
|
339
|
+
this.connectFuture = new Future(connectPromise);
|
351
340
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
}
|
341
|
+
this.connectFuture.promise.finally(() => (this.connectFuture = undefined));
|
342
|
+
|
343
|
+
return this.connectFuture.promise;
|
356
344
|
};
|
357
345
|
|
358
346
|
/**
|
@@ -360,11 +348,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
360
348
|
*/
|
361
349
|
disconnect = async (stopTracks = true) => {
|
362
350
|
log.info('disconnect from room', { identity: this.localParticipant.identity });
|
363
|
-
if (this.state === ConnectionState.Connecting) {
|
351
|
+
if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting) {
|
364
352
|
// try aborting pending connection attempt
|
365
353
|
log.warn('abort connection attempt');
|
366
354
|
this.abortController?.abort();
|
367
|
-
|
355
|
+
// in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
|
356
|
+
this.connectFuture?.reject(new ConnectionError('Client initiated disconnect'));
|
357
|
+
this.connectFuture = undefined;
|
368
358
|
}
|
369
359
|
// send leave
|
370
360
|
if (this.engine?.client.isConnected) {
|
@@ -374,7 +364,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
374
364
|
if (this.engine) {
|
375
365
|
this.engine.close();
|
376
366
|
}
|
377
|
-
|
378
367
|
this.handleDisconnect(stopTracks, DisconnectReason.CLIENT_INITIATED);
|
379
368
|
/* @ts-ignore */
|
380
369
|
this.engine = undefined;
|
@@ -592,6 +581,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
592
581
|
}
|
593
582
|
|
594
583
|
private handleRestarting = () => {
|
584
|
+
if (!this.reconnectFuture) {
|
585
|
+
this.reconnectFuture = new Future();
|
586
|
+
}
|
595
587
|
// also unwind existing participants & existing subscriptions
|
596
588
|
for (const p of this.participants.values()) {
|
597
589
|
this.handleParticipantDisconnected(p.sid, p);
|
@@ -608,6 +600,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
608
600
|
});
|
609
601
|
this.setAndEmitConnectionState(ConnectionState.Connected);
|
610
602
|
this.emit(RoomEvent.Reconnected);
|
603
|
+
this.reconnectFuture?.resolve();
|
604
|
+
this.reconnectFuture = undefined;
|
611
605
|
|
612
606
|
// rehydrate participants
|
613
607
|
if (joinResponse.participant) {
|
@@ -651,12 +645,32 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
651
645
|
if (this.state === ConnectionState.Disconnected) {
|
652
646
|
return;
|
653
647
|
}
|
648
|
+
// reject potentially ongoing reconnection attempt
|
649
|
+
if (this.connectFuture === this.reconnectFuture) {
|
650
|
+
this.connectFuture?.reject(undefined);
|
651
|
+
this.connectFuture = undefined;
|
652
|
+
this.reconnectFuture = undefined;
|
653
|
+
}
|
654
|
+
|
654
655
|
this.participants.forEach((p) => {
|
655
656
|
p.tracks.forEach((pub) => {
|
656
657
|
p.unpublishTrack(pub.trackSid);
|
657
658
|
});
|
658
659
|
});
|
659
660
|
|
661
|
+
this.localParticipant
|
662
|
+
.off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
|
663
|
+
.off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
|
664
|
+
.off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
|
665
|
+
.off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
|
666
|
+
.off(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished)
|
667
|
+
.off(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged)
|
668
|
+
.off(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError)
|
669
|
+
.off(
|
670
|
+
ParticipantEvent.ParticipantPermissionsChanged,
|
671
|
+
this.onLocalParticipantPermissionsChanged,
|
672
|
+
);
|
673
|
+
|
660
674
|
this.localParticipant.tracks.forEach((pub) => {
|
661
675
|
if (pub.track) {
|
662
676
|
this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
|
@@ -666,6 +680,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
666
680
|
pub.track?.stop();
|
667
681
|
}
|
668
682
|
});
|
683
|
+
|
669
684
|
this.localParticipant.tracks.clear();
|
670
685
|
this.localParticipant.videoTracks.clear();
|
671
686
|
this.localParticipant.audioTracks.clear();
|
@@ -1037,35 +1052,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1037
1052
|
}
|
1038
1053
|
}
|
1039
1054
|
|
1040
|
-
private setAndEmitConnectionState(state: ConnectionState
|
1055
|
+
private setAndEmitConnectionState(state: ConnectionState): boolean {
|
1041
1056
|
if (state === this.state) {
|
1042
1057
|
// unchanged
|
1043
1058
|
return false;
|
1044
1059
|
}
|
1045
|
-
switch (state) {
|
1046
|
-
case ConnectionState.Connecting:
|
1047
|
-
case ConnectionState.Reconnecting:
|
1048
|
-
if (!this.connectFuture) {
|
1049
|
-
// reuse existing connect future if possible
|
1050
|
-
this.connectFuture = new Future<void>();
|
1051
|
-
}
|
1052
|
-
break;
|
1053
|
-
case ConnectionState.Connected:
|
1054
|
-
if (this.connectFuture) {
|
1055
|
-
this.connectFuture.resolve();
|
1056
|
-
this.connectFuture = undefined;
|
1057
|
-
}
|
1058
|
-
break;
|
1059
|
-
case ConnectionState.Disconnected:
|
1060
|
-
if (this.connectFuture) {
|
1061
|
-
error ??= new Error('disconnected from Room');
|
1062
|
-
this.connectFuture.reject(error);
|
1063
|
-
this.connectFuture = undefined;
|
1064
|
-
}
|
1065
|
-
break;
|
1066
|
-
default:
|
1067
|
-
// nothing
|
1068
|
-
}
|
1069
1060
|
this.state = state;
|
1070
1061
|
this.emit(RoomEvent.ConnectionStateChanged, this.state);
|
1071
1062
|
return true;
|
@@ -1081,6 +1072,38 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1081
1072
|
return false;
|
1082
1073
|
}
|
1083
1074
|
|
1075
|
+
private onLocalParticipantMetadataChanged = (metadata: string | undefined) => {
|
1076
|
+
this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
|
1077
|
+
};
|
1078
|
+
|
1079
|
+
private onLocalTrackMuted = (pub: TrackPublication) => {
|
1080
|
+
this.emit(RoomEvent.TrackMuted, pub, this.localParticipant);
|
1081
|
+
};
|
1082
|
+
|
1083
|
+
private onLocalTrackUnmuted = (pub: TrackPublication) => {
|
1084
|
+
this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
|
1085
|
+
};
|
1086
|
+
|
1087
|
+
private onLocalTrackPublished = (pub: LocalTrackPublication) => {
|
1088
|
+
this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
|
1089
|
+
};
|
1090
|
+
|
1091
|
+
private onLocalTrackUnpublished = (pub: LocalTrackPublication) => {
|
1092
|
+
this.emit(RoomEvent.LocalTrackUnpublished, pub, this.localParticipant);
|
1093
|
+
};
|
1094
|
+
|
1095
|
+
private onLocalConnectionQualityChanged = (quality: ConnectionQuality) => {
|
1096
|
+
this.emit(RoomEvent.ConnectionQualityChanged, quality, this.localParticipant);
|
1097
|
+
};
|
1098
|
+
|
1099
|
+
private onMediaDevicesError = (e: Error) => {
|
1100
|
+
this.emit(RoomEvent.MediaDevicesError, e);
|
1101
|
+
};
|
1102
|
+
|
1103
|
+
private onLocalParticipantPermissionsChanged = (prevPermissions: ParticipantPermission) => {
|
1104
|
+
this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, this.localParticipant);
|
1105
|
+
};
|
1106
|
+
|
1084
1107
|
// /** @internal */
|
1085
1108
|
emit<E extends keyof RoomEventCallbacks>(
|
1086
1109
|
event: E,
|
package/src/room/events.ts
CHANGED
@@ -218,8 +218,8 @@ export enum RoomEvent {
|
|
218
218
|
* When we have encountered an error while attempting to create a track.
|
219
219
|
* The errors take place in getUserMedia().
|
220
220
|
* Use MediaDeviceFailure.getFailure(error) to get the reason of failure.
|
221
|
-
* [[
|
222
|
-
* an error while creating the audio or video track respectively.
|
221
|
+
* [[LocalParticipant.lastCameraError]] and [[LocalParticipant.lastMicrophoneError]]
|
222
|
+
* will indicate if it had an error while creating the audio or video track respectively.
|
223
223
|
*
|
224
224
|
* args: (error: Error)
|
225
225
|
*/
|
@@ -434,6 +434,23 @@ export default class LocalParticipant extends Participant {
|
|
434
434
|
if (opts.source) {
|
435
435
|
track.source = opts.source;
|
436
436
|
}
|
437
|
+
const existingTrackOfSource = Array.from(this.tracks.values()).find(
|
438
|
+
(publishedTrack) => track instanceof LocalTrack && publishedTrack.source === track.source,
|
439
|
+
);
|
440
|
+
if (existingTrackOfSource) {
|
441
|
+
try {
|
442
|
+
// throw an Error in order to capture the stack trace
|
443
|
+
throw Error(`publishing a second track with the same source: ${track.source}`);
|
444
|
+
} catch (e: unknown) {
|
445
|
+
if (e instanceof Error) {
|
446
|
+
log.warn(e.message, {
|
447
|
+
oldTrack: existingTrackOfSource,
|
448
|
+
newTrack: track,
|
449
|
+
trace: e.stack,
|
450
|
+
});
|
451
|
+
}
|
452
|
+
}
|
453
|
+
}
|
437
454
|
if (opts.stopMicTrackOnMute && track instanceof LocalAudioTrack) {
|
438
455
|
track.stopOnMute = true;
|
439
456
|
}
|
@@ -686,8 +703,6 @@ export default class LocalParticipant extends Participant {
|
|
686
703
|
}
|
687
704
|
|
688
705
|
track = publication.track;
|
689
|
-
|
690
|
-
track.sender = undefined;
|
691
706
|
track.off(TrackEvent.Muted, this.onTrackMuted);
|
692
707
|
track.off(TrackEvent.Unmuted, this.onTrackUnmuted);
|
693
708
|
track.off(TrackEvent.Ended, this.handleTrackEnded);
|
@@ -701,22 +716,31 @@ export default class LocalParticipant extends Participant {
|
|
701
716
|
track.stop();
|
702
717
|
}
|
703
718
|
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
719
|
+
if (
|
720
|
+
this.engine.publisher &&
|
721
|
+
this.engine.publisher.pc.connectionState !== 'closed' &&
|
722
|
+
track.sender
|
723
|
+
) {
|
724
|
+
try {
|
725
|
+
this.engine.removeTrack(track.sender);
|
726
|
+
if (track instanceof LocalVideoTrack) {
|
727
|
+
for (const [, trackInfo] of track.simulcastCodecs) {
|
728
|
+
if (trackInfo.sender) {
|
729
|
+
this.engine.removeTrack(trackInfo.sender);
|
730
|
+
trackInfo.sender = undefined;
|
731
|
+
}
|
715
732
|
}
|
733
|
+
track.simulcastCodecs.clear();
|
716
734
|
}
|
717
|
-
})
|
735
|
+
} catch (e) {
|
736
|
+
log.warn('failed to unpublish track', { error: e, method: 'unpublishTrack' });
|
737
|
+
} finally {
|
738
|
+
this.engine.negotiate();
|
739
|
+
}
|
718
740
|
}
|
719
741
|
|
742
|
+
track.sender = undefined;
|
743
|
+
|
720
744
|
// remove from our maps
|
721
745
|
this.tracks.delete(publication.trackSid);
|
722
746
|
switch (publication.kind) {
|
@@ -210,6 +210,20 @@ export default class RemoteParticipant extends Participant {
|
|
210
210
|
publication = new RemoteTrackPublication(kind, ti.sid, ti.name);
|
211
211
|
publication.updateInfo(ti);
|
212
212
|
newTracks.set(ti.sid, publication);
|
213
|
+
const existingTrackOfSource = Array.from(this.tracks.values()).find(
|
214
|
+
(publishedTrack) => publishedTrack.source === publication?.source,
|
215
|
+
);
|
216
|
+
if (existingTrackOfSource) {
|
217
|
+
log.warn(
|
218
|
+
`received a second track publication for ${this.identity} with the same source: ${publication.source}`,
|
219
|
+
{
|
220
|
+
oldTrack: existingTrackOfSource,
|
221
|
+
newTrack: publication,
|
222
|
+
participant: this,
|
223
|
+
participantInfo: info,
|
224
|
+
},
|
225
|
+
);
|
226
|
+
}
|
213
227
|
this.addTrackPublication(publication);
|
214
228
|
} else {
|
215
229
|
publication.updateInfo(ti);
|
@@ -27,17 +27,15 @@ export class SimulcastTrackInfo {
|
|
27
27
|
const refreshSubscribedCodecAfterNewCodec = 5000;
|
28
28
|
|
29
29
|
export default class LocalVideoTrack extends LocalTrack {
|
30
|
-
/* internal */
|
30
|
+
/* @internal */
|
31
31
|
signalClient?: SignalClient;
|
32
32
|
|
33
33
|
private prevStats?: Map<string, VideoSenderStats>;
|
34
34
|
|
35
35
|
private encodings?: RTCRtpEncodingParameters[];
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
SimulcastTrackInfo
|
40
|
-
>();
|
37
|
+
/* @internal */
|
38
|
+
simulcastCodecs: Map<VideoCodec, SimulcastTrackInfo> = new Map<VideoCodec, SimulcastTrackInfo>();
|
41
39
|
|
42
40
|
private subscribedCodecs?: SubscribedCodec[];
|
43
41
|
|
@@ -75,13 +73,10 @@ export default class LocalVideoTrack extends LocalTrack {
|
|
75
73
|
}
|
76
74
|
|
77
75
|
stop() {
|
78
|
-
this.sender = undefined;
|
79
76
|
this._mediaStreamTrack.getConstraints();
|
80
77
|
this.simulcastCodecs.forEach((trackInfo) => {
|
81
78
|
trackInfo.mediaStreamTrack.stop();
|
82
|
-
trackInfo.sender = undefined;
|
83
79
|
});
|
84
|
-
this.simulcastCodecs.clear();
|
85
80
|
super.stop();
|
86
81
|
}
|
87
82
|
|
@@ -8,7 +8,7 @@ import { TrackPublication } from './TrackPublication';
|
|
8
8
|
import { RemoteTrack } from './types';
|
9
9
|
|
10
10
|
export default class RemoteTrackPublication extends TrackPublication {
|
11
|
-
track?: RemoteTrack;
|
11
|
+
track?: RemoteTrack = undefined;
|
12
12
|
|
13
13
|
/** @internal */
|
14
14
|
protected allowed = true;
|
@@ -10,9 +10,6 @@ import { AdaptiveStreamSettings } from './types';
|
|
10
10
|
const REACTION_DELAY = 100;
|
11
11
|
|
12
12
|
export default class RemoteVideoTrack extends RemoteTrack {
|
13
|
-
/** @internal */
|
14
|
-
receiver?: RTCRtpReceiver;
|
15
|
-
|
16
13
|
private prevStats?: VideoReceiverStats;
|
17
14
|
|
18
15
|
private elementInfos: ElementInfo[] = [];
|