livekit-client 2.18.7 → 2.18.8
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.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +2 -2
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +391 -255
- 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.map +1 -1
- package/dist/src/logger.d.ts +11 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +13 -3
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +3 -1
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-track/LocalDataTrack.d.ts +31 -0
- package/dist/src/room/data-track/LocalDataTrack.d.ts.map +1 -1
- package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
- package/dist/src/room/data-track/handle.d.ts +1 -0
- package/dist/src/room/data-track/handle.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +4 -3
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +18 -3
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/types.d.ts +6 -0
- package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/utils/subscribeToEvents.d.ts.map +1 -1
- package/dist/ts4.2/logger.d.ts +11 -1
- package/dist/ts4.2/room/PCTransport.d.ts +13 -3
- package/dist/ts4.2/room/PCTransportManager.d.ts +3 -1
- package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +31 -0
- package/dist/ts4.2/room/data-track/handle.d.ts +1 -0
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +4 -3
- package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +18 -3
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +6 -0
- package/package.json +1 -1
- package/src/api/SignalClient.ts +19 -31
- package/src/logger.test.ts +61 -0
- package/src/logger.ts +38 -4
- package/src/room/PCTransport.ts +26 -3
- package/src/room/PCTransportManager.test.ts +281 -0
- package/src/room/PCTransportManager.ts +45 -31
- package/src/room/RTCEngine.ts +34 -52
- package/src/room/Room.ts +37 -59
- package/src/room/data-track/LocalDataTrack.ts +51 -0
- package/src/room/data-track/RemoteDataTrack.ts +4 -1
- package/src/room/data-track/handle.ts +4 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +72 -2
- package/src/room/data-track/incoming/IncomingDataTrackManager.ts +5 -3
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +235 -1
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +45 -3
- package/src/room/data-track/outgoing/types.ts +5 -0
- package/src/room/participant/LocalParticipant.ts +59 -144
- package/src/room/participant/Participant.ts +4 -1
- package/src/utils/subscribeToEvents.ts +11 -8
package/src/room/Room.ts
CHANGED
|
@@ -233,7 +233,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
233
233
|
this.sidToIdentity = new Map();
|
|
234
234
|
this.options = { ...roomOptionDefaults, ...options };
|
|
235
235
|
|
|
236
|
-
this.log = getLogger(this.options.loggerName ?? LoggerNames.Room);
|
|
236
|
+
this.log = getLogger(this.options.loggerName ?? LoggerNames.Room, () => this.logContext);
|
|
237
237
|
this.transcriptionReceivedTimes = new Map();
|
|
238
238
|
|
|
239
239
|
this.options.audioCaptureDefaults = {
|
|
@@ -290,8 +290,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
290
290
|
.on('trackUnpublished', (event) => {
|
|
291
291
|
this.emit(RoomEvent.LocalDataTrackUnpublished, event.sid);
|
|
292
292
|
})
|
|
293
|
-
.on('packetAvailable', ({ bytes }) => {
|
|
294
|
-
this.engine
|
|
293
|
+
.on('packetAvailable', ({ handle, bytes }) => {
|
|
294
|
+
this.engine
|
|
295
|
+
.sendLossyBytes(bytes, DataChannelKind.DATA_TRACK_LOSSY, 'wait')
|
|
296
|
+
.finally(() => this.outgoingDataTrackManager.handlePacketSendComplete(handle));
|
|
295
297
|
});
|
|
296
298
|
|
|
297
299
|
this.disconnectLock = new Mutex();
|
|
@@ -329,7 +331,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
329
331
|
this.switchActiveDevice(
|
|
330
332
|
'audiooutput',
|
|
331
333
|
unwrapConstraint(this.options.audioOutput.deviceId),
|
|
332
|
-
).catch((e) => this.log.warn(`Could not set audio output: ${e.message}
|
|
334
|
+
).catch((e) => this.log.warn(`Could not set audio output: ${e.message}`));
|
|
333
335
|
}
|
|
334
336
|
|
|
335
337
|
if (isWeb()) {
|
|
@@ -467,8 +469,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
467
469
|
return {
|
|
468
470
|
room: this.name,
|
|
469
471
|
roomID: this.roomInfo?.sid,
|
|
470
|
-
participant: this.localParticipant
|
|
471
|
-
participantID: this.localParticipant
|
|
472
|
+
participant: this.localParticipant?.identity,
|
|
473
|
+
participantID: this.localParticipant?.sid,
|
|
472
474
|
};
|
|
473
475
|
}
|
|
474
476
|
|
|
@@ -555,7 +557,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
555
557
|
.on(EngineEvent.Resuming, () => {
|
|
556
558
|
this.clearConnectionReconcile();
|
|
557
559
|
this.isResuming = true;
|
|
558
|
-
this.log.
|
|
560
|
+
this.log.debug('Resuming signal connection');
|
|
559
561
|
if (this.setAndEmitConnectionState(ConnectionState.SignalReconnecting)) {
|
|
560
562
|
this.emit(RoomEvent.SignalReconnecting);
|
|
561
563
|
}
|
|
@@ -563,7 +565,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
563
565
|
.on(EngineEvent.Resumed, () => {
|
|
564
566
|
this.registerConnectionReconcile();
|
|
565
567
|
this.isResuming = false;
|
|
566
|
-
this.log.
|
|
568
|
+
this.log.debug('Resumed signal connection');
|
|
567
569
|
this.updateSubscriptions();
|
|
568
570
|
this.emitBufferedEvents();
|
|
569
571
|
if (this.setAndEmitConnectionState(ConnectionState.Connected)) {
|
|
@@ -613,7 +615,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
613
615
|
if (!event.info) {
|
|
614
616
|
this.log.warn(
|
|
615
617
|
`received PublishDataTrackResponse, but event.info was ${event.info}, so skipping.`,
|
|
616
|
-
this.logContext,
|
|
617
618
|
);
|
|
618
619
|
return;
|
|
619
620
|
}
|
|
@@ -632,7 +633,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
632
633
|
if (!event.info) {
|
|
633
634
|
this.log.warn(
|
|
634
635
|
`received UnPublishDataTrackResponse, but event.info was ${event.info}, so skipping.`,
|
|
635
|
-
this.logContext,
|
|
636
636
|
);
|
|
637
637
|
return;
|
|
638
638
|
}
|
|
@@ -732,7 +732,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
732
732
|
if (this.state !== ConnectionState.Disconnected) {
|
|
733
733
|
return;
|
|
734
734
|
}
|
|
735
|
-
this.log.debug(`prepareConnection to ${url}
|
|
735
|
+
this.log.debug(`prepareConnection to ${url}`);
|
|
736
736
|
try {
|
|
737
737
|
if (isCloud(new URL(url)) && token) {
|
|
738
738
|
this.regionUrlProvider = new RegionUrlProvider(url, token);
|
|
@@ -742,13 +742,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
742
742
|
if (regionUrl && this.state === ConnectionState.Disconnected) {
|
|
743
743
|
this.regionUrl = regionUrl;
|
|
744
744
|
await fetch(toHttpUrl(regionUrl), { method: 'HEAD' });
|
|
745
|
-
this.log.debug(`prepared connection to ${regionUrl}
|
|
745
|
+
this.log.debug(`prepared connection to ${regionUrl}`);
|
|
746
746
|
}
|
|
747
747
|
} else {
|
|
748
748
|
await fetch(toHttpUrl(url), { method: 'HEAD' });
|
|
749
749
|
}
|
|
750
750
|
} catch (e) {
|
|
751
|
-
this.log.warn('could not prepare connection', {
|
|
751
|
+
this.log.warn('could not prepare connection', { error: e });
|
|
752
752
|
}
|
|
753
753
|
}
|
|
754
754
|
|
|
@@ -768,7 +768,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
768
768
|
|
|
769
769
|
if (this.state === ConnectionState.Connected) {
|
|
770
770
|
// when the state is reconnecting or connected, this function returns immediately
|
|
771
|
-
this.log.info(`already connected to room ${this.name}
|
|
771
|
+
this.log.info(`already connected to room ${this.name}`);
|
|
772
772
|
unlockDisconnect();
|
|
773
773
|
return Promise.resolve();
|
|
774
774
|
}
|
|
@@ -798,7 +798,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
798
798
|
this.regionUrlProvider?.setServerReportedRegions(settings);
|
|
799
799
|
})
|
|
800
800
|
.catch((e) => {
|
|
801
|
-
this.log.warn('could not fetch region settings', {
|
|
801
|
+
this.log.warn('could not fetch region settings', { error: e });
|
|
802
802
|
});
|
|
803
803
|
}
|
|
804
804
|
|
|
@@ -864,7 +864,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
864
864
|
if (nextUrl && !this.abortController?.signal.aborted) {
|
|
865
865
|
this.log.info(
|
|
866
866
|
`Initial connection failed with ConnectionError: ${error.message}. Retrying with another region: ${nextUrl}`,
|
|
867
|
-
this.logContext,
|
|
868
867
|
);
|
|
869
868
|
this.recreateEngine(true);
|
|
870
869
|
await connectFn(resolve, reject, nextUrl);
|
|
@@ -930,7 +929,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
930
929
|
}
|
|
931
930
|
|
|
932
931
|
if (serverInfo.version === '0.15.1' && this.options.dynacast) {
|
|
933
|
-
this.log.debug('disabling dynacast due to server version'
|
|
932
|
+
this.log.debug('disabling dynacast due to server version');
|
|
934
933
|
// dynacast has a bug in 0.15.1, so we cannot use it then
|
|
935
934
|
roomOptions.dynacast = false;
|
|
936
935
|
}
|
|
@@ -950,7 +949,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
950
949
|
this.e2eeManager.setSifTrailer(joinResponse.sifTrailer);
|
|
951
950
|
} catch (e: any) {
|
|
952
951
|
this.log.error(e instanceof Error ? e.message : 'Could not set SifTrailer', {
|
|
953
|
-
...this.logContext,
|
|
954
952
|
error: e,
|
|
955
953
|
});
|
|
956
954
|
}
|
|
@@ -975,7 +973,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
975
973
|
this.isResuming ||
|
|
976
974
|
this.engine?.pendingReconnect
|
|
977
975
|
) {
|
|
978
|
-
this.log.info('Reconnection attempt replaced by new connection attempt'
|
|
976
|
+
this.log.info('Reconnection attempt replaced by new connection attempt');
|
|
979
977
|
// make sure we close and recreate the existing engine in order to get rid of any potentially ongoing reconnection attempts
|
|
980
978
|
this.recreateEngine(true);
|
|
981
979
|
} else {
|
|
@@ -1027,7 +1025,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1027
1025
|
resultingError.status = err.status;
|
|
1028
1026
|
}
|
|
1029
1027
|
this.log.debug(`error trying to establish signal connection`, {
|
|
1030
|
-
...this.logContext,
|
|
1031
1028
|
error: err,
|
|
1032
1029
|
});
|
|
1033
1030
|
throw resultingError;
|
|
@@ -1077,12 +1074,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1077
1074
|
const unlock = await this.disconnectLock.lock();
|
|
1078
1075
|
try {
|
|
1079
1076
|
if (this.state === ConnectionState.Disconnected) {
|
|
1080
|
-
this.log.debug('already disconnected'
|
|
1077
|
+
this.log.debug('already disconnected');
|
|
1081
1078
|
return;
|
|
1082
1079
|
}
|
|
1083
|
-
this.log.info('disconnect from room'
|
|
1084
|
-
...this.logContext,
|
|
1085
|
-
});
|
|
1080
|
+
this.log.info('disconnect from room');
|
|
1086
1081
|
if (
|
|
1087
1082
|
this.state === ConnectionState.Connecting ||
|
|
1088
1083
|
this.state === ConnectionState.Reconnecting ||
|
|
@@ -1090,7 +1085,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1090
1085
|
) {
|
|
1091
1086
|
// try aborting pending connection attempt
|
|
1092
1087
|
const msg = 'Abort connection attempt due to user initiated disconnect';
|
|
1093
|
-
this.log.warn(msg
|
|
1088
|
+
this.log.warn(msg);
|
|
1094
1089
|
this.abortController?.abort(msg);
|
|
1095
1090
|
// in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
|
|
1096
1091
|
this.connectFuture?.reject?.(ConnectionError.cancelled('Client initiated disconnect'));
|
|
@@ -1256,7 +1251,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1256
1251
|
}
|
|
1257
1252
|
|
|
1258
1253
|
private onPageLeave = async () => {
|
|
1259
|
-
this.log.info('Page leave detected, disconnecting'
|
|
1254
|
+
this.log.info('Page leave detected, disconnecting');
|
|
1260
1255
|
await this.disconnect();
|
|
1261
1256
|
};
|
|
1262
1257
|
|
|
@@ -1299,7 +1294,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1299
1294
|
if (!document.hidden) {
|
|
1300
1295
|
this.log.debug(
|
|
1301
1296
|
'page visible again, triggering startAudio to resume playback and update playback status',
|
|
1302
|
-
this.logContext,
|
|
1303
1297
|
);
|
|
1304
1298
|
this.startAudio();
|
|
1305
1299
|
}
|
|
@@ -1359,7 +1353,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1359
1353
|
} else {
|
|
1360
1354
|
this.log.warn(
|
|
1361
1355
|
'Resuming video playback failed, make sure you call `startVideo` directly in a user gesture handler',
|
|
1362
|
-
this.logContext,
|
|
1363
1356
|
);
|
|
1364
1357
|
}
|
|
1365
1358
|
});
|
|
@@ -1545,11 +1538,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1545
1538
|
return;
|
|
1546
1539
|
}
|
|
1547
1540
|
if (this.state === ConnectionState.Disconnected) {
|
|
1548
|
-
this.log.warn('skipping incoming track after Room disconnected'
|
|
1541
|
+
this.log.warn('skipping incoming track after Room disconnected');
|
|
1549
1542
|
return;
|
|
1550
1543
|
}
|
|
1551
1544
|
if (mediaTrack.readyState === 'ended') {
|
|
1552
|
-
this.log.
|
|
1545
|
+
this.log.debug('skipping incoming track as it already ended');
|
|
1553
1546
|
return;
|
|
1554
1547
|
}
|
|
1555
1548
|
const parts = unpackStreamId(stream.id);
|
|
@@ -1561,7 +1554,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1561
1554
|
if (streamId && streamId.startsWith('TR')) trackId = streamId;
|
|
1562
1555
|
|
|
1563
1556
|
if (participantSid === this.localParticipant.sid) {
|
|
1564
|
-
this.log.warn('tried to create RemoteParticipant for local participant'
|
|
1557
|
+
this.log.warn('tried to create RemoteParticipant for local participant');
|
|
1565
1558
|
return;
|
|
1566
1559
|
}
|
|
1567
1560
|
|
|
@@ -1574,7 +1567,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1574
1567
|
if (participantSid.startsWith('PA')) {
|
|
1575
1568
|
this.log.error(
|
|
1576
1569
|
`Tried to add a track for a participant, that's not present. Sid: ${participantSid}`,
|
|
1577
|
-
this.logContext,
|
|
1578
1570
|
);
|
|
1579
1571
|
}
|
|
1580
1572
|
return;
|
|
@@ -1588,7 +1580,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1588
1580
|
if (!id) {
|
|
1589
1581
|
this.log.error(
|
|
1590
1582
|
`Tried to add a track whose 'sid' could not be found for a participant, that's not present. Sid: ${participantSid}`,
|
|
1591
|
-
this.logContext,
|
|
1592
1583
|
);
|
|
1593
1584
|
return;
|
|
1594
1585
|
}
|
|
@@ -1598,7 +1589,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1598
1589
|
if (!trackId.startsWith('TR')) {
|
|
1599
1590
|
this.log.warn(
|
|
1600
1591
|
`Tried to add a track whose 'sid' could not be determined for a participant, that's not present. Sid: ${participantSid}, streamId: ${streamId}, trackId: ${trackId}`,
|
|
1601
|
-
{
|
|
1592
|
+
{ remoteParticipantID: participantSid, streamId, trackId },
|
|
1602
1593
|
);
|
|
1603
1594
|
}
|
|
1604
1595
|
|
|
@@ -1645,7 +1636,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1645
1636
|
// the subscription before publishTrack has finished adding the publication.
|
|
1646
1637
|
// defer with a timeout until LocalTrackPublished fires for the matching trackSid
|
|
1647
1638
|
this.log.debug('deferring LocalTrackSubscribed, publication not yet available', {
|
|
1648
|
-
...this.logContext,
|
|
1649
1639
|
subscribedSid,
|
|
1650
1640
|
});
|
|
1651
1641
|
|
|
@@ -1677,7 +1667,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1677
1667
|
} else {
|
|
1678
1668
|
this.log.warn(
|
|
1679
1669
|
'could not find local track publication for LocalTrackSubscribed event after timeout',
|
|
1680
|
-
{
|
|
1670
|
+
{ subscribedSid },
|
|
1681
1671
|
);
|
|
1682
1672
|
}
|
|
1683
1673
|
}, TIMEOUT_MS);
|
|
@@ -1710,7 +1700,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1710
1700
|
|
|
1711
1701
|
private handleSignalRestarted = async (joinResponse: JoinResponse) => {
|
|
1712
1702
|
this.log.debug(`signal reconnected to server, region ${joinResponse.serverRegion}`, {
|
|
1713
|
-
...this.logContext,
|
|
1714
1703
|
region: joinResponse.serverRegion,
|
|
1715
1704
|
});
|
|
1716
1705
|
this.bufferedEvents = [];
|
|
@@ -1721,18 +1710,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1721
1710
|
// unpublish & republish tracks
|
|
1722
1711
|
await this.localParticipant.republishAllTracks(undefined, true);
|
|
1723
1712
|
} catch (error) {
|
|
1724
|
-
this.log.error('error trying to re-publish tracks after reconnection', {
|
|
1725
|
-
...this.logContext,
|
|
1726
|
-
error,
|
|
1727
|
-
});
|
|
1713
|
+
this.log.error('error trying to re-publish tracks after reconnection', { error });
|
|
1728
1714
|
}
|
|
1729
1715
|
|
|
1730
1716
|
try {
|
|
1731
1717
|
await this.engine.waitForRestarted();
|
|
1732
|
-
this.log.debug(`fully reconnected to server`, {
|
|
1733
|
-
...this.logContext,
|
|
1734
|
-
region: joinResponse.serverRegion,
|
|
1735
|
-
});
|
|
1718
|
+
this.log.debug(`fully reconnected to server`, { region: joinResponse.serverRegion });
|
|
1736
1719
|
} catch {
|
|
1737
1720
|
// reconnection failed, handleDisconnect is being invoked already, just return here
|
|
1738
1721
|
return;
|
|
@@ -1749,6 +1732,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1749
1732
|
this.bufferedEvents = [];
|
|
1750
1733
|
this.transcriptionReceivedTimes.clear();
|
|
1751
1734
|
this.incomingDataStreamManager.clearControllers();
|
|
1735
|
+
this.incomingDataTrackManager.reset();
|
|
1736
|
+
this.outgoingDataTrackManager.reset();
|
|
1752
1737
|
if (this.state === ConnectionState.Disconnected) {
|
|
1753
1738
|
return;
|
|
1754
1739
|
}
|
|
@@ -2158,7 +2143,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2158
2143
|
};
|
|
2159
2144
|
|
|
2160
2145
|
private handleAudioPlaybackFailed = (e: any) => {
|
|
2161
|
-
this.log.warn('could not playback audio', {
|
|
2146
|
+
this.log.warn('could not playback audio', { error: e });
|
|
2162
2147
|
if (!this.canPlaybackAudio) {
|
|
2163
2148
|
return;
|
|
2164
2149
|
}
|
|
@@ -2304,7 +2289,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2304
2289
|
try {
|
|
2305
2290
|
await Promise.race([this.audioContext.resume(), sleep(200)]);
|
|
2306
2291
|
} catch (e: any) {
|
|
2307
|
-
this.log.warn('Could not resume audio context', {
|
|
2292
|
+
this.log.warn('Could not resume audio context', { error: e });
|
|
2308
2293
|
}
|
|
2309
2294
|
}
|
|
2310
2295
|
|
|
@@ -2347,7 +2332,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2347
2332
|
if (this.options.audioOutput?.deviceId) {
|
|
2348
2333
|
participant
|
|
2349
2334
|
.setAudioOutput(this.options.audioOutput)
|
|
2350
|
-
.catch((e) => this.log.warn(`Could not set audio output: ${e.message}
|
|
2335
|
+
.catch((e) => this.log.warn(`Could not set audio output: ${e.message}`));
|
|
2351
2336
|
}
|
|
2352
2337
|
return participant;
|
|
2353
2338
|
}
|
|
@@ -2506,7 +2491,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2506
2491
|
) {
|
|
2507
2492
|
consecutiveFailures++;
|
|
2508
2493
|
this.log.warn('detected connection state mismatch', {
|
|
2509
|
-
...this.logContext,
|
|
2510
2494
|
numFailures: consecutiveFailures,
|
|
2511
2495
|
engine: this.engine
|
|
2512
2496
|
? {
|
|
@@ -2539,6 +2523,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2539
2523
|
// unchanged
|
|
2540
2524
|
return false;
|
|
2541
2525
|
}
|
|
2526
|
+
this.log.info(`connection state changed: ${this.state} -> ${state}`);
|
|
2542
2527
|
this.state = state;
|
|
2543
2528
|
this.incomingDataStreamManager.setConnected(state === ConnectionState.Connected);
|
|
2544
2529
|
this.emit(RoomEvent.ConnectionStateChanged, this.state);
|
|
@@ -2633,10 +2618,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2633
2618
|
deviceId &&
|
|
2634
2619
|
deviceId !== this.localParticipant.activeDeviceMap.get(deviceKind)
|
|
2635
2620
|
) {
|
|
2636
|
-
this.log.debug(
|
|
2637
|
-
`local track restarted, setting ${deviceKind} ${deviceId} active`,
|
|
2638
|
-
this.logContext,
|
|
2639
|
-
);
|
|
2621
|
+
this.log.debug(`local track restarted, setting ${deviceKind} ${deviceId} active`);
|
|
2640
2622
|
this.localParticipant.activeDeviceMap.set(deviceKind, deviceId);
|
|
2641
2623
|
this.emit(RoomEvent.ActiveDeviceChanged, deviceKind, deviceId);
|
|
2642
2624
|
}
|
|
@@ -2815,13 +2797,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2815
2797
|
// only extract logContext from arguments in order to avoid logging the whole object tree
|
|
2816
2798
|
const minimizedArgs = mapArgs(args).filter((arg: unknown) => arg !== undefined);
|
|
2817
2799
|
if (event === RoomEvent.TrackSubscribed || event === RoomEvent.TrackUnsubscribed) {
|
|
2818
|
-
this.log.trace(`subscribe trace: ${event}`, {
|
|
2819
|
-
...this.logContext,
|
|
2820
|
-
event,
|
|
2821
|
-
args: minimizedArgs,
|
|
2822
|
-
});
|
|
2800
|
+
this.log.trace(`subscribe trace: ${event}`, { event, args: minimizedArgs });
|
|
2823
2801
|
}
|
|
2824
|
-
this.log.debug(`room event ${event}`, {
|
|
2802
|
+
this.log.debug(`room event ${event}`, { event, args: minimizedArgs });
|
|
2825
2803
|
}
|
|
2826
2804
|
return super.emit(event, ...args);
|
|
2827
2805
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import log, { LoggerNames, type StructuredLogger, getLogger } from '../../logger';
|
|
2
|
+
import { Future } from '../utils';
|
|
2
3
|
import { type DataTrackFrame, DataTrackFrameInternal } from './frame';
|
|
3
4
|
import type { DataTrackHandle } from './handle';
|
|
4
5
|
import type OutgoingDataTrackManager from './outgoing/OutgoingDataTrackManager';
|
|
@@ -28,14 +29,35 @@ export default class LocalDataTrack implements ILocalTrack, IDataTrack {
|
|
|
28
29
|
|
|
29
30
|
protected log: StructuredLogger = log;
|
|
30
31
|
|
|
32
|
+
/** Resolves once the data track has sent all pending packets the rtc data channel buffer. */
|
|
33
|
+
protected flushedFuture = new Future<void, never>();
|
|
34
|
+
|
|
31
35
|
/** @internal */
|
|
32
36
|
constructor(options: DataTrackOptions, manager: OutgoingDataTrackManager) {
|
|
33
37
|
this.options = options;
|
|
34
38
|
this.manager = manager;
|
|
35
39
|
|
|
36
40
|
this.log = getLogger(LoggerNames.DataTracks);
|
|
41
|
+
|
|
42
|
+
this.manager.on('packetsFlushed', this.handleManagerPacketsFlushed);
|
|
43
|
+
this.manager.on('reset', this.handleManagerReset);
|
|
37
44
|
}
|
|
38
45
|
|
|
46
|
+
private handleManagerReset = () => {
|
|
47
|
+
// When the associated manager resets, mark any in flight flushes as complete
|
|
48
|
+
// There's nothing actionable a user can do to get these to complete so no
|
|
49
|
+
// error is being thrown.
|
|
50
|
+
this.handleManagerPacketsFlushed();
|
|
51
|
+
|
|
52
|
+
this.manager.off('packetsFlushed', this.handleManagerReset);
|
|
53
|
+
this.manager.off('reset', this.handleManagerReset);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
private handleManagerPacketsFlushed = () => {
|
|
57
|
+
this.flushedFuture.resolve?.();
|
|
58
|
+
this.flushedFuture = new Future();
|
|
59
|
+
};
|
|
60
|
+
|
|
39
61
|
/** @internal */
|
|
40
62
|
static withExplicitHandle(
|
|
41
63
|
options: DataTrackOptions,
|
|
@@ -104,6 +126,35 @@ export default class LocalDataTrack implements ILocalTrack, IDataTrack {
|
|
|
104
126
|
}
|
|
105
127
|
}
|
|
106
128
|
|
|
129
|
+
/**
|
|
130
|
+
* When called, waits for all in flight packets to be sent before resolving.
|
|
131
|
+
*
|
|
132
|
+
* Use this to:
|
|
133
|
+
*
|
|
134
|
+
* 1. Send frames exactly in order:
|
|
135
|
+
* ```ts
|
|
136
|
+
* await track.tryPush(/* ... *\/);
|
|
137
|
+
* await track.flush();
|
|
138
|
+
* await track.tryPush(/* ... *\/);
|
|
139
|
+
* await track.flush();
|
|
140
|
+
* // ... etc ...
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* 2. Wait for frames to all be delivered before unpublishing a local data track:
|
|
144
|
+
*
|
|
145
|
+
* ```ts
|
|
146
|
+
* await track.tryPush(/* ... *\/);
|
|
147
|
+
* await track.tryPush(/* ... *\/);
|
|
148
|
+
* await track.tryPush(/* ... *\/);
|
|
149
|
+
* // ... etc ...
|
|
150
|
+
* await track.flush();
|
|
151
|
+
* await track.unpublish();
|
|
152
|
+
* ```
|
|
153
|
+
**/
|
|
154
|
+
async flush(): Promise<void> {
|
|
155
|
+
return this.flushedFuture.promise;
|
|
156
|
+
}
|
|
157
|
+
|
|
107
158
|
/**
|
|
108
159
|
* Unpublish the track from the SFU. Once this is called, any further calls to {@link tryPush}
|
|
109
160
|
* will fail.
|
|
@@ -66,11 +66,14 @@ export default class RemoteDataTrack implements IRemoteTrack, IDataTrack {
|
|
|
66
66
|
*/
|
|
67
67
|
subscribe(options?: DataTrackSubscribeOptions): ReadableStream<DataTrackFrame> {
|
|
68
68
|
try {
|
|
69
|
-
const [stream] = this.manager.openSubscriptionStream(
|
|
69
|
+
const [stream, sfuSubscriptionComplete] = this.manager.openSubscriptionStream(
|
|
70
70
|
this.info.sid,
|
|
71
71
|
options?.signal,
|
|
72
72
|
options?.bufferSize,
|
|
73
73
|
);
|
|
74
|
+
// Prevent uncaught promise rejections from bubbling up if rejections occur after the
|
|
75
|
+
// readable stream is discarded.
|
|
76
|
+
sfuSubscriptionComplete.catch(() => {});
|
|
74
77
|
return stream;
|
|
75
78
|
} catch (err) {
|
|
76
79
|
// NOTE: Rethrow errors to break Throws<...> type boundary
|
|
@@ -327,7 +327,7 @@ describe('DataTrackIncomingManager', () => {
|
|
|
327
327
|
expect(sfuUpdateSubscriptionEvent.subscribe).toStrictEqual(false);
|
|
328
328
|
|
|
329
329
|
// 7. Make sure shutting down the manager doesn't throw errors
|
|
330
|
-
manager.
|
|
330
|
+
manager.reset();
|
|
331
331
|
});
|
|
332
332
|
|
|
333
333
|
it('should NOT terminate the sfu subscription if the abortsignal is triggered on one of two active subscriptions', async () => {
|
|
@@ -675,7 +675,7 @@ describe('DataTrackIncomingManager', () => {
|
|
|
675
675
|
);
|
|
676
676
|
|
|
677
677
|
// 4. Shutdown the manager, and make sure it doesn't throw
|
|
678
|
-
manager.
|
|
678
|
+
manager.reset();
|
|
679
679
|
|
|
680
680
|
// 5. Make sure the trackUnpublished event fires for the descriptor
|
|
681
681
|
const trackUnpublishedEvent = await managerEvents.waitFor('trackUnpublished');
|
|
@@ -866,5 +866,75 @@ describe('DataTrackIncomingManager', () => {
|
|
|
866
866
|
// 8. Make sure the in flight stream is now complete
|
|
867
867
|
await expect(reader.read()).resolves.toStrictEqual({ value: undefined, done: true });
|
|
868
868
|
});
|
|
869
|
+
|
|
870
|
+
it(`should not produce an unhandled promise rejection when RemoteDataTrack.subscribe()'s signal is aborted`, async () => {
|
|
871
|
+
const manager = new IncomingDataTrackManager();
|
|
872
|
+
const managerEvents = subscribeToEvents<DataTrackIncomingManagerCallbacks>(manager, [
|
|
873
|
+
'sfuUpdateSubscription',
|
|
874
|
+
'trackPublished',
|
|
875
|
+
]);
|
|
876
|
+
|
|
877
|
+
const sid = 'data track sid';
|
|
878
|
+
|
|
879
|
+
// 1. Register the data track so we can get a RemoteDataTrack via trackPublished.
|
|
880
|
+
await manager.receiveSfuPublicationUpdates(
|
|
881
|
+
new Map([
|
|
882
|
+
[
|
|
883
|
+
'identity',
|
|
884
|
+
[{ sid, pubHandle: DataTrackHandle.fromNumber(5), name: 'test', usesE2ee: false }],
|
|
885
|
+
],
|
|
886
|
+
]),
|
|
887
|
+
);
|
|
888
|
+
const { track } = await managerEvents.waitFor('trackPublished');
|
|
889
|
+
|
|
890
|
+
// 2. Listen for unhandled rejections (coming from sfuSubscriptionComplete) and throw
|
|
891
|
+
// them so the test will terminate.
|
|
892
|
+
const onUnhandled = (reason: unknown) => {
|
|
893
|
+
throw reason;
|
|
894
|
+
};
|
|
895
|
+
process.on('unhandledRejection', onUnhandled);
|
|
896
|
+
|
|
897
|
+
try {
|
|
898
|
+
const controller = new AbortController();
|
|
899
|
+
const stream = track.subscribe({ signal: controller.signal });
|
|
900
|
+
|
|
901
|
+
// 3. Consume the stream the way a user would, catching the rejection.
|
|
902
|
+
const caughtByUser: unknown[] = [];
|
|
903
|
+
const consumerDone = (async () => {
|
|
904
|
+
try {
|
|
905
|
+
const reader = stream.getReader();
|
|
906
|
+
while (true) {
|
|
907
|
+
const { done } = await reader.read();
|
|
908
|
+
if (done) {
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
} catch (err) {
|
|
913
|
+
caughtByUser.push(err);
|
|
914
|
+
}
|
|
915
|
+
})();
|
|
916
|
+
|
|
917
|
+
// Wait until subscribeRequest has kicked off so we abort during the
|
|
918
|
+
// 'pending' state — the path that rejects sfuSubscriptionComplete.
|
|
919
|
+
await managerEvents.waitFor('sfuUpdateSubscription');
|
|
920
|
+
|
|
921
|
+
// 4. Abort the subscription
|
|
922
|
+
controller.abort();
|
|
923
|
+
await consumerDone;
|
|
924
|
+
|
|
925
|
+
// Drain microtasks so any unhandledRejection has a chance to fire.
|
|
926
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
927
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
928
|
+
|
|
929
|
+
// 5. Make sure that no `unhandledrejection`s occur and get bubbled up as user
|
|
930
|
+
// facing errors.
|
|
931
|
+
|
|
932
|
+
// But, the error should still get raised by the user so they can catch it / do with it as
|
|
933
|
+
// they please.
|
|
934
|
+
expect(caughtByUser).toHaveLength(1);
|
|
935
|
+
} finally {
|
|
936
|
+
process.off('unhandledRejection', onUnhandled);
|
|
937
|
+
}
|
|
938
|
+
});
|
|
869
939
|
});
|
|
870
940
|
});
|
|
@@ -92,7 +92,7 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
|
|
|
92
92
|
*
|
|
93
93
|
* This is an index that allows track descriptors to be looked up
|
|
94
94
|
* by subscriber handle in O(1) time, to make routing incoming packets
|
|
95
|
-
* a
|
|
95
|
+
* (a hot code path) faster.
|
|
96
96
|
*/
|
|
97
97
|
private subscriptionHandles = new Map<DataTrackHandle, DataTrackSid>();
|
|
98
98
|
|
|
@@ -626,8 +626,9 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
|
|
|
626
626
|
}
|
|
627
627
|
}
|
|
628
628
|
|
|
629
|
-
/**
|
|
630
|
-
|
|
629
|
+
/** Resets the manager, ending any subscriptions, and getting it ready for the next room
|
|
630
|
+
* connection. */
|
|
631
|
+
reset() {
|
|
631
632
|
for (const descriptor of this.descriptors.values()) {
|
|
632
633
|
this.emit('trackUnpublished', {
|
|
633
634
|
sid: descriptor.info.sid,
|
|
@@ -643,5 +644,6 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
|
|
|
643
644
|
}
|
|
644
645
|
}
|
|
645
646
|
this.descriptors.clear();
|
|
647
|
+
this.subscriptionHandles.clear();
|
|
646
648
|
}
|
|
647
649
|
}
|