livekit-client 1.2.3 → 1.2.6
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 +1002 -778
- 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/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/DeviceManager.d.ts +1 -0
- package/dist/src/room/DeviceManager.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +4 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +1 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +4 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- 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 +36 -34
- package/src/room/DeviceManager.ts +23 -1
- package/src/room/RTCEngine.ts +30 -6
- package/src/room/Room.ts +149 -152
- package/src/room/participant/LocalParticipant.ts +13 -3
- package/src/room/participant/RemoteParticipant.ts +14 -13
- package/src/room/track/LocalAudioTrack.ts +0 -2
- package/src/room/track/LocalVideoTrack.ts +3 -7
- 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
|
@@ -156,12 +160,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
156
160
|
.on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
|
157
161
|
.on(EngineEvent.DataPacketReceived, this.handleDataPacket)
|
158
162
|
.on(EngineEvent.Resuming, () => {
|
163
|
+
if (!this.reconnectFuture) {
|
164
|
+
this.reconnectFuture = new Future();
|
165
|
+
}
|
159
166
|
if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
|
160
167
|
this.emit(RoomEvent.Reconnecting);
|
161
168
|
}
|
162
169
|
})
|
163
170
|
.on(EngineEvent.Resumed, () => {
|
164
171
|
this.setAndEmitConnectionState(ConnectionState.Connected);
|
172
|
+
this.reconnectFuture?.resolve();
|
173
|
+
this.reconnectFuture = undefined;
|
165
174
|
this.emit(RoomEvent.Reconnected);
|
166
175
|
this.updateSubscriptions();
|
167
176
|
})
|
@@ -193,145 +202,144 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
193
202
|
return DeviceManager.getInstance().getDevices(kind, requestPermissions);
|
194
203
|
}
|
195
204
|
|
196
|
-
connect =
|
205
|
+
connect = (url: string, token: string, opts?: RoomConnectOptions): Promise<void> => {
|
197
206
|
if (this.state === ConnectionState.Connected) {
|
198
207
|
// when the state is reconnecting or connected, this function returns immediately
|
199
208
|
log.warn(`already connected to room ${this.name}`);
|
200
|
-
return;
|
209
|
+
return Promise.resolve();
|
201
210
|
}
|
202
211
|
|
203
212
|
if (this.connectFuture) {
|
204
213
|
return this.connectFuture.promise;
|
214
|
+
} else if (this.reconnectFuture) {
|
215
|
+
this.connectFuture = this.reconnectFuture;
|
216
|
+
return this.connectFuture.promise;
|
205
217
|
}
|
206
|
-
|
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
|
+
}
|
207
223
|
|
208
|
-
|
209
|
-
this.
|
210
|
-
}
|
224
|
+
// recreate engine if previously disconnected
|
225
|
+
this.createEngine();
|
211
226
|
|
212
|
-
|
213
|
-
this.createEngine();
|
227
|
+
this.acquireAudioContext();
|
214
228
|
|
215
|
-
|
229
|
+
if (opts?.rtcConfig) {
|
230
|
+
this.engine.rtcConfig = opts.rtcConfig;
|
231
|
+
}
|
216
232
|
|
217
|
-
|
218
|
-
|
219
|
-
|
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,
|
246
|
+
},
|
247
|
+
this.abortController.signal,
|
248
|
+
);
|
249
|
+
log.debug(
|
250
|
+
`connected to Livekit Server version: ${joinResponse.serverVersion}, region: ${joinResponse.serverRegion}`,
|
251
|
+
);
|
220
252
|
|
221
|
-
|
253
|
+
if (!joinResponse.serverVersion) {
|
254
|
+
throw new UnsupportedServer('unknown server version');
|
255
|
+
}
|
222
256
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
autoSubscribe: opts?.autoSubscribe,
|
229
|
-
publishOnly: opts?.publishOnly,
|
230
|
-
adaptiveStream:
|
231
|
-
typeof this.options?.adaptiveStream === 'object' ? true : this.options?.adaptiveStream,
|
232
|
-
},
|
233
|
-
this.abortController.signal,
|
234
|
-
);
|
235
|
-
log.debug(
|
236
|
-
`connected to Livekit Server version: ${joinResponse.serverVersion}, region: ${joinResponse.serverRegion}`,
|
237
|
-
);
|
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
|
+
}
|
238
262
|
|
239
|
-
|
240
|
-
|
241
|
-
|
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
|
+
);
|
242
282
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
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
|
+
});
|
248
297
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
.on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
|
260
|
-
.on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
|
261
|
-
.on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished)
|
262
|
-
.on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged)
|
263
|
-
.on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError)
|
264
|
-
.on(
|
265
|
-
ParticipantEvent.ParticipantPermissionsChanged,
|
266
|
-
this.onLocalParticipantPermissionsChanged,
|
267
|
-
);
|
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
|
+
return;
|
307
|
+
}
|
268
308
|
|
269
|
-
//
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
)
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
309
|
+
// don't return until ICE connected
|
310
|
+
const connectTimeout = setTimeout(() => {
|
311
|
+
// timeout
|
312
|
+
this.recreateEngine();
|
313
|
+
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
314
|
+
reject(new ConnectionError('could not connect PeerConnection after timeout'));
|
315
|
+
}, maxICEConnectTimeout);
|
316
|
+
const abortHandler = () => {
|
317
|
+
log.warn('closing engine');
|
318
|
+
clearTimeout(connectTimeout);
|
319
|
+
this.recreateEngine();
|
320
|
+
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
321
|
+
reject(new ConnectionError('room connection has been cancelled'));
|
322
|
+
};
|
323
|
+
if (this.abortController?.signal.aborted) {
|
324
|
+
abortHandler();
|
325
|
+
}
|
326
|
+
this.abortController?.signal.addEventListener('abort', abortHandler);
|
327
|
+
|
328
|
+
this.engine.once(EngineEvent.Connected, () => {
|
329
|
+
clearTimeout(connectTimeout);
|
330
|
+
this.abortController?.signal.removeEventListener('abort', abortHandler);
|
331
|
+
// also hook unload event
|
332
|
+
if (isWeb()) {
|
333
|
+
window.addEventListener('beforeunload', this.onBeforeUnload);
|
334
|
+
navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange);
|
281
335
|
}
|
336
|
+
this.setAndEmitConnectionState(ConnectionState.Connected);
|
337
|
+
resolve();
|
282
338
|
});
|
339
|
+
}).finally(() => (this.connectFuture = undefined));
|
340
|
+
this.connectFuture = new Future(connectPromise);
|
283
341
|
|
284
|
-
|
285
|
-
this.sid = joinResponse.room!.sid;
|
286
|
-
this.metadata = joinResponse.room!.metadata;
|
287
|
-
this.emit(RoomEvent.SignalConnected);
|
288
|
-
} catch (err) {
|
289
|
-
this.recreateEngine();
|
290
|
-
this.setAndEmitConnectionState(
|
291
|
-
ConnectionState.Disconnected,
|
292
|
-
new ConnectionError('could not establish signal connection'),
|
293
|
-
);
|
294
|
-
throw err;
|
295
|
-
}
|
296
|
-
|
297
|
-
// don't return until ICE connected
|
298
|
-
const connectTimeout = setTimeout(() => {
|
299
|
-
// timeout
|
300
|
-
this.recreateEngine();
|
301
|
-
this.setAndEmitConnectionState(
|
302
|
-
ConnectionState.Disconnected,
|
303
|
-
new ConnectionError('could not connect PeerConnection after timeout'),
|
304
|
-
);
|
305
|
-
}, maxICEConnectTimeout);
|
306
|
-
const abortHandler = () => {
|
307
|
-
log.warn('closing engine');
|
308
|
-
clearTimeout(connectTimeout);
|
309
|
-
this.recreateEngine();
|
310
|
-
this.setAndEmitConnectionState(
|
311
|
-
ConnectionState.Disconnected,
|
312
|
-
new ConnectionError('room connection has been cancelled'),
|
313
|
-
);
|
314
|
-
};
|
315
|
-
if (this.abortController?.signal.aborted) {
|
316
|
-
abortHandler();
|
317
|
-
}
|
318
|
-
this.abortController?.signal.addEventListener('abort', abortHandler);
|
319
|
-
|
320
|
-
this.engine.once(EngineEvent.Connected, () => {
|
321
|
-
clearTimeout(connectTimeout);
|
322
|
-
this.abortController?.signal.removeEventListener('abort', abortHandler);
|
323
|
-
// also hook unload event
|
324
|
-
if (isWeb()) {
|
325
|
-
window.addEventListener('beforeunload', this.onBeforeUnload);
|
326
|
-
navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange);
|
327
|
-
}
|
328
|
-
this.setAndEmitConnectionState(ConnectionState.Connected);
|
329
|
-
});
|
330
|
-
|
331
|
-
if (this.connectFuture) {
|
332
|
-
/** @ts-ignore */
|
333
|
-
return this.connectFuture.promise;
|
334
|
-
}
|
342
|
+
return this.connectFuture.promise;
|
335
343
|
};
|
336
344
|
|
337
345
|
/**
|
@@ -339,11 +347,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
339
347
|
*/
|
340
348
|
disconnect = async (stopTracks = true) => {
|
341
349
|
log.info('disconnect from room', { identity: this.localParticipant.identity });
|
342
|
-
if (this.state === ConnectionState.Connecting) {
|
350
|
+
if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting) {
|
343
351
|
// try aborting pending connection attempt
|
344
352
|
log.warn('abort connection attempt');
|
345
353
|
this.abortController?.abort();
|
346
|
-
|
354
|
+
// in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
|
355
|
+
this.connectFuture?.reject(new ConnectionError('Client initiated disconnect'));
|
356
|
+
this.connectFuture = undefined;
|
347
357
|
}
|
348
358
|
// send leave
|
349
359
|
if (this.engine?.client.isConnected) {
|
@@ -353,7 +363,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
353
363
|
if (this.engine) {
|
354
364
|
this.engine.close();
|
355
365
|
}
|
356
|
-
|
357
366
|
this.handleDisconnect(stopTracks, DisconnectReason.CLIENT_INITIATED);
|
358
367
|
/* @ts-ignore */
|
359
368
|
this.engine = undefined;
|
@@ -532,8 +541,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
532
541
|
// at that time, ICE connectivity has not been established so the track is not
|
533
542
|
// technically subscribed.
|
534
543
|
// We'll defer these events until when the room is connected or eventually disconnected.
|
535
|
-
if (this.connectFuture) {
|
536
|
-
this.connectFuture.promise.then(() => {
|
544
|
+
if (this.connectFuture || this.reconnectFuture) {
|
545
|
+
Promise.allSettled([this.connectFuture?.promise, this.reconnectFuture?.promise]).then(() => {
|
537
546
|
this.onTrackAdded(mediaTrack, stream, receiver);
|
538
547
|
});
|
539
548
|
return;
|
@@ -571,6 +580,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
571
580
|
}
|
572
581
|
|
573
582
|
private handleRestarting = () => {
|
583
|
+
if (!this.reconnectFuture) {
|
584
|
+
this.reconnectFuture = new Future();
|
585
|
+
}
|
574
586
|
// also unwind existing participants & existing subscriptions
|
575
587
|
for (const p of this.participants.values()) {
|
576
588
|
this.handleParticipantDisconnected(p.sid, p);
|
@@ -587,6 +599,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
587
599
|
});
|
588
600
|
this.setAndEmitConnectionState(ConnectionState.Connected);
|
589
601
|
this.emit(RoomEvent.Reconnected);
|
602
|
+
this.reconnectFuture?.resolve();
|
603
|
+
this.reconnectFuture = undefined;
|
590
604
|
|
591
605
|
// rehydrate participants
|
592
606
|
if (joinResponse.participant) {
|
@@ -630,6 +644,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
630
644
|
if (this.state === ConnectionState.Disconnected) {
|
631
645
|
return;
|
632
646
|
}
|
647
|
+
// reject potentially ongoing reconnection attempt
|
648
|
+
if (this.connectFuture === this.reconnectFuture) {
|
649
|
+
this.connectFuture?.reject(undefined);
|
650
|
+
this.connectFuture = undefined;
|
651
|
+
this.reconnectFuture = undefined;
|
652
|
+
}
|
653
|
+
|
633
654
|
this.participants.forEach((p) => {
|
634
655
|
p.tracks.forEach((pub) => {
|
635
656
|
p.unpublishTrack(pub.trackSid);
|
@@ -704,10 +725,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
704
725
|
// when it's disconnected, send updates
|
705
726
|
if (info.state === ParticipantInfo_State.DISCONNECTED) {
|
706
727
|
this.handleParticipantDisconnected(info.sid, remoteParticipant);
|
707
|
-
} else if (isNewParticipant) {
|
708
|
-
// fire connected event
|
709
|
-
this.emitWhenConnected(RoomEvent.ParticipantConnected, remoteParticipant);
|
710
|
-
} else {
|
728
|
+
} else if (!isNewParticipant) {
|
711
729
|
// just update, no events
|
712
730
|
remoteParticipant.updateInfo(info);
|
713
731
|
}
|
@@ -907,6 +925,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
907
925
|
this.participants.set(id, participant);
|
908
926
|
if (info) {
|
909
927
|
this.identityToSid.set(info.identity, info.sid);
|
928
|
+
// if we have valid info and the participant wasn't in the map before, we can assume the participant is new
|
929
|
+
// firing here to make sure that `ParticipantConnected` fires before the initial track events
|
930
|
+
this.emitWhenConnected(RoomEvent.ParticipantConnected, participant);
|
910
931
|
}
|
911
932
|
|
912
933
|
// also forward events
|
@@ -1030,35 +1051,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1030
1051
|
}
|
1031
1052
|
}
|
1032
1053
|
|
1033
|
-
private setAndEmitConnectionState(state: ConnectionState
|
1054
|
+
private setAndEmitConnectionState(state: ConnectionState): boolean {
|
1034
1055
|
if (state === this.state) {
|
1035
1056
|
// unchanged
|
1036
1057
|
return false;
|
1037
1058
|
}
|
1038
|
-
switch (state) {
|
1039
|
-
case ConnectionState.Connecting:
|
1040
|
-
case ConnectionState.Reconnecting:
|
1041
|
-
if (!this.connectFuture) {
|
1042
|
-
// reuse existing connect future if possible
|
1043
|
-
this.connectFuture = new Future<void>();
|
1044
|
-
}
|
1045
|
-
break;
|
1046
|
-
case ConnectionState.Connected:
|
1047
|
-
if (this.connectFuture) {
|
1048
|
-
this.connectFuture.resolve();
|
1049
|
-
this.connectFuture = undefined;
|
1050
|
-
}
|
1051
|
-
break;
|
1052
|
-
case ConnectionState.Disconnected:
|
1053
|
-
if (this.connectFuture) {
|
1054
|
-
error ??= new Error('disconnected from Room');
|
1055
|
-
this.connectFuture.reject(error);
|
1056
|
-
this.connectFuture = undefined;
|
1057
|
-
}
|
1058
|
-
break;
|
1059
|
-
default:
|
1060
|
-
// nothing
|
1061
|
-
}
|
1062
1059
|
this.state = state;
|
1063
1060
|
this.emit(RoomEvent.ConnectionStateChanged, this.state);
|
1064
1061
|
return true;
|
@@ -722,10 +722,20 @@ export default class LocalParticipant extends Participant {
|
|
722
722
|
track.sender
|
723
723
|
) {
|
724
724
|
try {
|
725
|
-
this.engine.
|
726
|
-
|
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
|
+
}
|
732
|
+
}
|
733
|
+
track.simulcastCodecs.clear();
|
734
|
+
}
|
727
735
|
} catch (e) {
|
728
|
-
log.warn('failed to
|
736
|
+
log.warn('failed to unpublish track', { error: e, method: 'unpublishTrack' });
|
737
|
+
} finally {
|
738
|
+
this.engine.negotiate();
|
729
739
|
}
|
730
740
|
}
|
731
741
|
|
@@ -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);
|
@@ -220,19 +234,6 @@ export default class RemoteParticipant extends Participant {
|
|
220
234
|
// always emit events for new publications, Room will not forward them unless it's ready
|
221
235
|
newTracks.forEach((publication) => {
|
222
236
|
this.emit(ParticipantEvent.TrackPublished, publication);
|
223
|
-
const existingTrackOfSource = Array.from(this.tracks.values()).find(
|
224
|
-
(publishedTrack) => publishedTrack.source === publication.source,
|
225
|
-
);
|
226
|
-
if (existingTrackOfSource) {
|
227
|
-
log.warn(
|
228
|
-
`received a second track publication for ${this.identity} with the same source: ${publication.source}`,
|
229
|
-
{
|
230
|
-
oldTrack: existingTrackOfSource,
|
231
|
-
newTrack: publication,
|
232
|
-
participant: this,
|
233
|
-
},
|
234
|
-
);
|
235
|
-
}
|
236
237
|
});
|
237
238
|
|
238
239
|
// detect removed tracks
|
@@ -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
|
|
@@ -78,9 +76,7 @@ export default class LocalVideoTrack extends LocalTrack {
|
|
78
76
|
this._mediaStreamTrack.getConstraints();
|
79
77
|
this.simulcastCodecs.forEach((trackInfo) => {
|
80
78
|
trackInfo.mediaStreamTrack.stop();
|
81
|
-
trackInfo.sender = undefined;
|
82
79
|
});
|
83
|
-
this.simulcastCodecs.clear();
|
84
80
|
super.stop();
|
85
81
|
}
|
86
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[] = [];
|
package/src/room/track/create.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import DeviceManager from '../DeviceManager';
|
1
2
|
import { TrackInvalidError } from '../errors';
|
2
3
|
import { mediaTrackToLocalTrack } from '../participant/publishUtils';
|
3
4
|
import { audioDefaults, videoDefaults } from './defaults';
|
@@ -30,7 +31,21 @@ export async function createLocalTracks(
|
|
30
31
|
|
31
32
|
const opts = mergeDefaultOptions(options, audioDefaults, videoDefaults);
|
32
33
|
const constraints = constraintsForOptions(opts);
|
33
|
-
|
34
|
+
|
35
|
+
// Keep a reference to the promise on DeviceManager and await it in getLocalDevices()
|
36
|
+
// works around iOS Safari Bug https://bugs.webkit.org/show_bug.cgi?id=179363
|
37
|
+
const mediaPromise = navigator.mediaDevices.getUserMedia(constraints);
|
38
|
+
|
39
|
+
if (options.audio) {
|
40
|
+
DeviceManager.userMediaPromiseMap.set('audioinput', mediaPromise);
|
41
|
+
mediaPromise.catch(() => DeviceManager.userMediaPromiseMap.delete('audioinput'));
|
42
|
+
}
|
43
|
+
if (options.video) {
|
44
|
+
DeviceManager.userMediaPromiseMap.set('videoinput', mediaPromise);
|
45
|
+
mediaPromise.catch(() => DeviceManager.userMediaPromiseMap.delete('videoinput'));
|
46
|
+
}
|
47
|
+
|
48
|
+
const stream = await mediaPromise;
|
34
49
|
return stream.getTracks().map((mediaStreamTrack) => {
|
35
50
|
const isAudio = mediaStreamTrack.kind === 'audio';
|
36
51
|
let trackOptions = isAudio ? options!.audio : options!.video;
|
package/src/room/utils.ts
CHANGED
@@ -122,10 +122,12 @@ export class Future<T> {
|
|
122
122
|
|
123
123
|
reject!: (e: any) => void;
|
124
124
|
|
125
|
-
constructor() {
|
126
|
-
this.promise =
|
127
|
-
|
128
|
-
|
129
|
-
|
125
|
+
constructor(promise?: Promise<T>) {
|
126
|
+
this.promise =
|
127
|
+
promise ??
|
128
|
+
new Promise<T>((resolve, reject) => {
|
129
|
+
this.resolve = resolve;
|
130
|
+
this.reject = reject;
|
131
|
+
});
|
130
132
|
}
|
131
133
|
}
|