livekit-client 2.22.3 → 2.22.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- 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 -1
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +602 -247
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.fm.worker.js +1 -1
- package/dist/livekit-client.fm.worker.js.map +1 -1
- package/dist/livekit-client.fm.worker.mjs +2 -1
- package/dist/livekit-client.fm.worker.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 +4 -2
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/options.d.ts +14 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +78 -7
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +8 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/defaults.d.ts.map +1 -1
- package/dist/src/room/errors.d.ts +18 -0
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +3 -2
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +2 -0
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts +13 -0
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +32 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/compressionStreamStubs.d.ts +12 -0
- package/dist/src/test/compressionStreamStubs.d.ts.map +1 -0
- package/dist/ts4.2/api/SignalClient.d.ts +187 -185
- package/dist/ts4.2/api/SignalClientStateMachine.d.ts +85 -85
- package/dist/ts4.2/api/WebSocketStream.d.ts +33 -33
- package/dist/ts4.2/api/utils.d.ts +9 -9
- package/dist/ts4.2/connectionHelper/ConnectionCheck.d.ts +30 -30
- package/dist/ts4.2/connectionHelper/checks/Checker.d.ts +62 -62
- package/dist/ts4.2/connectionHelper/checks/cloudRegion.d.ts +18 -18
- package/dist/ts4.2/connectionHelper/checks/connectionProtocol.d.ts +20 -20
- package/dist/ts4.2/connectionHelper/checks/publishAudio.d.ts +6 -6
- package/dist/ts4.2/connectionHelper/checks/publishVideo.d.ts +7 -7
- package/dist/ts4.2/connectionHelper/checks/reconnect.d.ts +6 -6
- package/dist/ts4.2/connectionHelper/checks/turn.d.ts +6 -6
- package/dist/ts4.2/connectionHelper/checks/webrtc.d.ts +6 -6
- package/dist/ts4.2/connectionHelper/checks/websocket.d.ts +6 -6
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +111 -111
- package/dist/ts4.2/e2ee/KeyProvider.d.ts +54 -54
- package/dist/ts4.2/e2ee/constants.d.ts +21 -21
- package/dist/ts4.2/e2ee/errors.d.ts +12 -12
- package/dist/ts4.2/e2ee/events.d.ts +39 -39
- package/dist/ts4.2/e2ee/index.d.ts +6 -6
- package/dist/ts4.2/e2ee/types.d.ts +231 -231
- package/dist/ts4.2/e2ee/utils.d.ts +32 -32
- package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +16 -16
- package/dist/ts4.2/e2ee/worker/ErrorRateLimiter.d.ts +21 -21
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +209 -209
- package/dist/ts4.2/e2ee/worker/ParticipantKeyHandler.d.ts +81 -84
- package/dist/ts4.2/e2ee/worker/e2ee.worker.d.ts +2 -2
- package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +28 -28
- package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +23 -23
- package/dist/ts4.2/frameMetadata/FrameMetadataManager.d.ts +53 -53
- package/dist/ts4.2/frameMetadata/frameMetadata.d.ts +33 -33
- package/dist/ts4.2/frameMetadata/types.d.ts +62 -62
- package/dist/ts4.2/frameMetadata/utils.d.ts +9 -9
- package/dist/ts4.2/frameMetadata/worker/frameMetadata.worker.d.ts +2 -2
- package/dist/ts4.2/index.d.ts +76 -76
- package/dist/ts4.2/logger.d.ts +66 -66
- package/dist/ts4.2/options.d.ts +163 -149
- package/dist/ts4.2/room/BackOffStrategy.d.ts +27 -27
- package/dist/ts4.2/room/DefaultReconnectPolicy.d.ts +8 -8
- package/dist/ts4.2/room/DeviceManager.d.ts +12 -12
- package/dist/ts4.2/room/PCTransport.d.ts +246 -175
- package/dist/ts4.2/room/PCTransportManager.d.ts +77 -77
- package/dist/ts4.2/room/RTCEngine.d.ts +269 -262
- package/dist/ts4.2/room/ReconnectPolicy.d.ts +24 -24
- package/dist/ts4.2/room/RegionUrlProvider.d.ts +36 -36
- package/dist/ts4.2/room/Room.d.ts +354 -365
- package/dist/ts4.2/room/attribute-typings.d.ts +35 -35
- package/dist/ts4.2/room/data-channel/DataChannelManager.d.ts +62 -62
- package/dist/ts4.2/room/data-channel/FlowControlledDataChannel.d.ts +94 -95
- package/dist/ts4.2/room/data-channel/LossyDataChannel.d.ts +41 -41
- package/dist/ts4.2/room/data-channel/ReliableDataChannel.d.ts +57 -57
- package/dist/ts4.2/room/data-channel/types.d.ts +12 -12
- package/dist/ts4.2/room/data-stream/compression.d.ts +49 -49
- package/dist/ts4.2/room/data-stream/constants.d.ts +18 -18
- package/dist/ts4.2/room/data-stream/incoming/IncomingDataStreamManager.d.ts +29 -29
- package/dist/ts4.2/room/data-stream/incoming/StreamReader.d.ts +80 -80
- package/dist/ts4.2/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +73 -73
- package/dist/ts4.2/room/data-stream/outgoing/StreamWriter.d.ts +16 -16
- package/dist/ts4.2/room/data-stream/outgoing/header-utils.d.ts +18 -18
- package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +79 -80
- package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +50 -50
- package/dist/ts4.2/room/data-track/depacketizer.d.ts +59 -59
- package/dist/ts4.2/room/data-track/frame.d.ts +22 -22
- package/dist/ts4.2/room/data-track/handle.d.ts +27 -27
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +124 -124
- package/dist/ts4.2/room/data-track/incoming/errors.d.ts +24 -24
- package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +41 -41
- package/dist/ts4.2/room/data-track/incoming/types.d.ts +20 -20
- package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +126 -126
- package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +74 -74
- package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +23 -23
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +48 -48
- package/dist/ts4.2/room/data-track/packet/constants.d.ts +20 -20
- package/dist/ts4.2/room/data-track/packet/errors.d.ts +40 -40
- package/dist/ts4.2/room/data-track/packet/extensions.d.ts +69 -69
- package/dist/ts4.2/room/data-track/packet/index.d.ts +98 -98
- package/dist/ts4.2/room/data-track/packet/serializable.d.ts +13 -13
- package/dist/ts4.2/room/data-track/packetizer.d.ts +43 -43
- package/dist/ts4.2/room/data-track/track-interfaces.d.ts +23 -23
- package/dist/ts4.2/room/data-track/types.d.ts +22 -22
- package/dist/ts4.2/room/data-track/utils.d.ts +60 -60
- package/dist/ts4.2/room/debounce.d.ts +11 -11
- package/dist/ts4.2/room/defaults.d.ts +9 -9
- package/dist/ts4.2/room/errors.d.ts +180 -162
- package/dist/ts4.2/room/events.d.ts +638 -637
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +287 -287
- package/dist/ts4.2/room/participant/Participant.d.ts +142 -144
- package/dist/ts4.2/room/participant/ParticipantTrackPermission.d.ts +26 -26
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +85 -86
- package/dist/ts4.2/room/participant/publishUtils.d.ts +53 -53
- package/dist/ts4.2/room/rpc/client/RpcClientManager.d.ts +43 -43
- package/dist/ts4.2/room/rpc/client/events.d.ts +8 -8
- package/dist/ts4.2/room/rpc/index.d.ts +7 -7
- package/dist/ts4.2/room/rpc/server/RpcServerManager.d.ts +44 -44
- package/dist/ts4.2/room/rpc/server/events.d.ts +8 -8
- package/dist/ts4.2/room/rpc/utils.d.ts +131 -131
- package/dist/ts4.2/room/stats.d.ts +71 -71
- package/dist/ts4.2/room/statsSummary.d.ts +13 -13
- package/dist/ts4.2/room/timers.d.ts +12 -12
- package/dist/ts4.2/room/token-source/TokenSource.d.ts +82 -82
- package/dist/ts4.2/room/token-source/types.d.ts +70 -70
- package/dist/ts4.2/room/token-source/utils.d.ts +8 -8
- package/dist/ts4.2/room/track/FrameMetadataExtractor.d.ts +19 -19
- package/dist/ts4.2/room/track/LocalAudioTrack.d.ts +39 -42
- package/dist/ts4.2/room/track/LocalTrack.d.ts +125 -125
- package/dist/ts4.2/room/track/LocalTrackPublication.d.ts +42 -42
- package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +102 -100
- package/dist/ts4.2/room/track/RemoteAudioTrack.d.ts +55 -55
- package/dist/ts4.2/room/track/RemoteTrack.d.ts +38 -38
- package/dist/ts4.2/room/track/RemoteTrackPublication.d.ts +72 -75
- package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +73 -76
- package/dist/ts4.2/room/track/Track.d.ts +156 -166
- package/dist/ts4.2/room/track/TrackPublication.d.ts +75 -81
- package/dist/ts4.2/room/track/create.d.ts +25 -25
- package/dist/ts4.2/room/track/facingMode.d.ts +41 -41
- package/dist/ts4.2/room/track/options.d.ts +403 -403
- package/dist/ts4.2/room/track/processor/types.d.ts +31 -31
- package/dist/ts4.2/room/track/record.d.ts +13 -13
- package/dist/ts4.2/room/track/types.d.ts +28 -28
- package/dist/ts4.2/room/track/utils.d.ts +60 -47
- package/dist/ts4.2/room/types.d.ts +127 -127
- package/dist/ts4.2/room/utils.d.ts +246 -214
- package/dist/ts4.2/test/MockMediaStreamTrack.d.ts +26 -26
- package/dist/ts4.2/test/compressionStreamStubs.d.ts +12 -0
- package/dist/ts4.2/test/mocks.d.ts +12 -12
- package/dist/ts4.2/test/promiseState.d.ts +12 -12
- package/dist/ts4.2/test/signalToken.d.ts +26 -26
- package/dist/ts4.2/type-polyfills/non-shared-typed-arrays.d.ts +2 -2
- package/dist/ts4.2/utils/AsyncQueue.d.ts +23 -23
- package/dist/ts4.2/utils/TypedPromise.d.ts +17 -17
- package/dist/ts4.2/utils/abort-signal-polyfill.d.ts +13 -13
- package/dist/ts4.2/utils/browserParser.d.ts +13 -13
- package/dist/ts4.2/utils/camelToSnakeCase.d.ts +8 -8
- package/dist/ts4.2/utils/cloneDeep.d.ts +2 -2
- package/dist/ts4.2/utils/dataPacketBuffer.d.ts +34 -34
- package/dist/ts4.2/utils/deferrable-map.d.ts +32 -32
- package/dist/ts4.2/utils/machineInspector.d.ts +54 -54
- package/dist/ts4.2/utils/serializer.d.ts +48 -48
- package/dist/ts4.2/utils/subscribeToEvents.d.ts +15 -15
- package/dist/ts4.2/utils/ttlmap.d.ts +20 -20
- package/dist/ts4.2/version.d.ts +15 -15
- package/package.json +5 -4
- package/src/api/SignalClient.e2e.test.ts +1 -3
- package/src/api/SignalClient.test.ts +160 -10
- package/src/api/SignalClient.ts +75 -54
- package/src/options.ts +16 -0
- package/src/room/PCTransport.test.ts +183 -7
- package/src/room/PCTransport.ts +197 -36
- package/src/room/PCTransportManager.ts +3 -1
- package/src/room/RTCEngine.test.ts +84 -0
- package/src/room/RTCEngine.ts +24 -5
- package/src/room/Room.test.ts +32 -6
- package/src/room/Room.ts +26 -6
- package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +16 -20
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +5 -5
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.test.ts +60 -39
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +4 -4
- package/src/room/defaults.ts +1 -0
- package/src/room/errors.test.ts +36 -0
- package/src/room/errors.ts +27 -0
- package/src/room/events.ts +2 -1
- package/src/room/participant/publishUtils.ts +0 -19
- package/src/room/rpc/client/RpcClientManager.test.ts +51 -0
- package/src/room/rpc/client/RpcClientManager.ts +32 -15
- package/src/room/track/LocalTrack.ts +18 -7
- package/src/room/track/LocalVideoTrack.ts +10 -3
- package/src/room/track/RemoteAudioTrack.ts +3 -3
- package/src/room/track/RemoteVideoTrack.test.ts +210 -0
- package/src/room/track/RemoteVideoTrack.ts +29 -3
- package/src/room/track/utils.test.ts +201 -2
- package/src/room/track/utils.ts +88 -0
- package/src/room/utils.test.ts +124 -0
- package/src/room/utils.ts +70 -0
- package/src/test/compressionStreamStubs.ts +53 -0
package/src/room/RTCEngine.ts
CHANGED
|
@@ -268,6 +268,14 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
268
268
|
/** timestamp (ms) the primary transport entered `CONNECTING`, used to bound how long we tolerate it */
|
|
269
269
|
private transportConnectingSince?: number;
|
|
270
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Abort handlers for the in-flight `negotiate()` calls, which a single central
|
|
273
|
+
* Closing/Restarting listener pair fans out to. Registering a listener pair per call instead
|
|
274
|
+
* accumulates them on the engine emitter and trips its max-listener warning under
|
|
275
|
+
* renegotiation bursts.
|
|
276
|
+
*/
|
|
277
|
+
private pendingNegotiationAborts = new Set<() => void>();
|
|
278
|
+
|
|
271
279
|
constructor(private options: InternalRoomOptions) {
|
|
272
280
|
super();
|
|
273
281
|
this.log = getLogger(options.loggerName ?? LoggerNames.Engine, () => this.logContext);
|
|
@@ -308,6 +316,15 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
308
316
|
this.client.onParticipantUpdate = (updates) =>
|
|
309
317
|
this.emit(EngineEvent.ParticipantUpdate, updates);
|
|
310
318
|
this.client.onJoined = (joinResponse) => this.emit(EngineEvent.Joined, joinResponse);
|
|
319
|
+
|
|
320
|
+
const abortPendingNegotiations = () => {
|
|
321
|
+
// Iterate a copy: each handler removes itself from the set as its negotiation settles.
|
|
322
|
+
for (const abort of Array.from(this.pendingNegotiationAborts)) {
|
|
323
|
+
abort();
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
this.on(EngineEvent.Closing, abortPendingNegotiations);
|
|
327
|
+
this.on(EngineEvent.Restarting, abortPendingNegotiations);
|
|
311
328
|
}
|
|
312
329
|
|
|
313
330
|
/** @internal */
|
|
@@ -669,6 +686,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
669
686
|
this.midToTrackId = midToTrackId;
|
|
670
687
|
}
|
|
671
688
|
await this.pcManager.setPublisherAnswer(sd, offerId);
|
|
689
|
+
this.emit(EngineEvent.RequestSubscribedCodecRefresh);
|
|
672
690
|
};
|
|
673
691
|
|
|
674
692
|
// add candidate on trickle
|
|
@@ -738,7 +756,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
738
756
|
|
|
739
757
|
this.client.onMediaSectionsRequirement = (requirement: MediaSectionsRequirement) => {
|
|
740
758
|
this.addMediaSections(requirement.numAudios, requirement.numVideos);
|
|
741
|
-
this.negotiate()
|
|
759
|
+
this.negotiate().catch((err) => {
|
|
760
|
+
this.log.error(err);
|
|
761
|
+
});
|
|
742
762
|
};
|
|
743
763
|
|
|
744
764
|
this.client.onPublishDataTrackResponse = (event: PublishDataTrackResponse) => {
|
|
@@ -1807,8 +1827,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1807
1827
|
if (this.isClosed) {
|
|
1808
1828
|
reject(new NegotiationError('cannot negotiate on closed engine'));
|
|
1809
1829
|
}
|
|
1810
|
-
this.
|
|
1811
|
-
this.on(EngineEvent.Restarting, handleClosed);
|
|
1830
|
+
this.pendingNegotiationAborts.add(handleClosed);
|
|
1812
1831
|
this.pcManager.publisher.off(PCEvents.RTPVideoPayloadTypes, this.onRtpMapAvailable);
|
|
1813
1832
|
this.pcManager.publisher.once(PCEvents.RTPVideoPayloadTypes, this.onRtpMapAvailable);
|
|
1814
1833
|
|
|
@@ -1832,8 +1851,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1832
1851
|
reject(new Error(String(e)));
|
|
1833
1852
|
}
|
|
1834
1853
|
} finally {
|
|
1835
|
-
this.
|
|
1836
|
-
this.off(EngineEvent.Restarting, handleClosed);
|
|
1854
|
+
this.pendingNegotiationAborts.delete(handleClosed);
|
|
1837
1855
|
}
|
|
1838
1856
|
});
|
|
1839
1857
|
}
|
|
@@ -2095,6 +2113,7 @@ export type EngineEventCallbacks = {
|
|
|
2095
2113
|
joined: (joinResponse: JoinResponse) => void;
|
|
2096
2114
|
tokenRefreshed: (token: string) => void;
|
|
2097
2115
|
serverRegionsReported: (regions: RegionSettings) => void;
|
|
2116
|
+
requestSubscribedCodecRefresh: () => void;
|
|
2098
2117
|
};
|
|
2099
2118
|
|
|
2100
2119
|
export interface RegionStrategy {
|
package/src/room/Room.test.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '@livekit/protocol';
|
|
11
11
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
12
12
|
import MockMediaStreamTrack from '../test/MockMediaStreamTrack';
|
|
13
|
+
import { stubMissingDeflateRawSupport } from '../test/compressionStreamStubs';
|
|
13
14
|
import Room, { ConnectionState } from './Room';
|
|
14
15
|
import { roomConnectOptionDefaults, roomOptionDefaults } from './defaults';
|
|
15
16
|
import { EngineEvent, ParticipantEvent, RoomEvent } from './events';
|
|
@@ -46,8 +47,8 @@ describe('Active device switch', () => {
|
|
|
46
47
|
});
|
|
47
48
|
|
|
48
49
|
describe('Room signaling options', () => {
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
/** Drives `connectSignal` with E2EE enabled against a stub engine, returning the `join` mock. */
|
|
51
|
+
async function connectSignalWithE2EE(room: Room) {
|
|
51
52
|
const join = vi.fn().mockResolvedValue({
|
|
52
53
|
joinResponse: new JoinResponse({
|
|
53
54
|
room: { name: 'test-room', sid: 'room-sid' },
|
|
@@ -91,19 +92,44 @@ describe('Room signaling options', () => {
|
|
|
91
92
|
new AbortController(),
|
|
92
93
|
);
|
|
93
94
|
|
|
95
|
+
return join;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function expectCapabilities(
|
|
99
|
+
join: ReturnType<typeof vi.fn>,
|
|
100
|
+
clientInfoCapabilities: Array<ClientInfo_Capability>,
|
|
101
|
+
) {
|
|
94
102
|
expect(join).toHaveBeenCalledWith(
|
|
95
103
|
'wss://test.livekit.io',
|
|
96
104
|
'test-token',
|
|
97
105
|
expect.objectContaining({
|
|
98
|
-
clientInfoCapabilities
|
|
99
|
-
ClientInfo_Capability.CAP_PACKET_TRAILER,
|
|
100
|
-
ClientInfo_Capability.CAP_COMPRESSION_DEFLATE_RAW,
|
|
101
|
-
],
|
|
106
|
+
clientInfoCapabilities,
|
|
102
107
|
e2eeEnabled: true,
|
|
103
108
|
}),
|
|
104
109
|
expect.any(AbortSignal),
|
|
105
110
|
false,
|
|
106
111
|
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
it('advertises packet trailer capability when E2EE can handle trailers', async () => {
|
|
115
|
+
const join = await connectSignalWithE2EE(new Room());
|
|
116
|
+
|
|
117
|
+
expectCapabilities(join, [
|
|
118
|
+
ClientInfo_Capability.CAP_PACKET_TRAILER,
|
|
119
|
+
ClientInfo_Capability.CAP_COMPRESSION_DEFLATE_RAW,
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('does not advertise deflate-raw compression when the runtime cannot decompress it', async () => {
|
|
124
|
+
const restoreCompressionStreams = stubMissingDeflateRawSupport();
|
|
125
|
+
try {
|
|
126
|
+
const join = await connectSignalWithE2EE(new Room());
|
|
127
|
+
|
|
128
|
+
// Otherwise peers would compress what they send us and we'd have to drop it.
|
|
129
|
+
expectCapabilities(join, [ClientInfo_Capability.CAP_PACKET_TRAILER]);
|
|
130
|
+
} finally {
|
|
131
|
+
restoreCompressionStreams();
|
|
132
|
+
}
|
|
107
133
|
});
|
|
108
134
|
});
|
|
109
135
|
|
package/src/room/Room.ts
CHANGED
|
@@ -77,6 +77,7 @@ import {
|
|
|
77
77
|
ConnectionErrorReason,
|
|
78
78
|
UnexpectedConnectionState,
|
|
79
79
|
UnsupportedServer,
|
|
80
|
+
canFailOverToAnotherRegion,
|
|
80
81
|
} from './errors';
|
|
81
82
|
import { EngineEvent, ParticipantEvent, RoomEvent, TrackEvent } from './events';
|
|
82
83
|
import LocalParticipant from './participant/LocalParticipant';
|
|
@@ -120,9 +121,10 @@ import {
|
|
|
120
121
|
getEmptyAudioStreamTrack,
|
|
121
122
|
isBrowserSupported,
|
|
122
123
|
isCloud,
|
|
123
|
-
|
|
124
|
+
isDeflateRawCompressionSupported,
|
|
124
125
|
isLocalAudioTrack,
|
|
125
126
|
isLocalParticipant,
|
|
127
|
+
isLocalVideoTrack,
|
|
126
128
|
isReactNative,
|
|
127
129
|
isRemotePub,
|
|
128
130
|
isSafariBased,
|
|
@@ -755,6 +757,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
755
757
|
updatedAtInMs: Date.now(),
|
|
756
758
|
maxAgeInMs: DEFAULT_MAX_AGE_MS,
|
|
757
759
|
});
|
|
760
|
+
})
|
|
761
|
+
.on(EngineEvent.RequestSubscribedCodecRefresh, () => {
|
|
762
|
+
for (const videoPub of this.localParticipant.videoTrackPublications.values()) {
|
|
763
|
+
if (isLocalVideoTrack(videoPub.track)) {
|
|
764
|
+
videoPub.track.refreshSubscribedCodecs();
|
|
765
|
+
}
|
|
766
|
+
}
|
|
758
767
|
});
|
|
759
768
|
|
|
760
769
|
if (this.localParticipant) {
|
|
@@ -908,8 +917,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
908
917
|
if (
|
|
909
918
|
this.regionUrlProvider &&
|
|
910
919
|
error instanceof ConnectionError &&
|
|
911
|
-
error
|
|
912
|
-
error.reason !== ConnectionErrorReason.NotAllowed
|
|
920
|
+
canFailOverToAnotherRegion(error)
|
|
913
921
|
) {
|
|
914
922
|
let nextUrl: string | null = null;
|
|
915
923
|
try {
|
|
@@ -992,6 +1000,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
992
1000
|
autoSubscribe: connectOptions.autoSubscribe,
|
|
993
1001
|
adaptiveStream:
|
|
994
1002
|
typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
|
|
1003
|
+
disableIceLite: connectOptions.disableIceLite,
|
|
1004
|
+
subscriberAllowPause: connectOptions.subscriberAllowPause,
|
|
995
1005
|
clientInfoCapabilities: this.getClientInfoCapabilities(roomOptions),
|
|
996
1006
|
maxRetries: connectOptions.maxRetries,
|
|
997
1007
|
e2eeEnabled: !!this.e2eeManager,
|
|
@@ -1211,8 +1221,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1211
1221
|
let req: SimulateScenario | undefined;
|
|
1212
1222
|
switch (scenario) {
|
|
1213
1223
|
case 'signal-reconnect':
|
|
1224
|
+
const reconnectDelay = typeof arg === 'number' ? arg : 0;
|
|
1214
1225
|
// @ts-expect-error function is private
|
|
1215
|
-
await this.engine.client.handleOnClose('simulate disconnect');
|
|
1226
|
+
await this.engine.client.handleOnClose('simulate disconnect', undefined, reconnectDelay);
|
|
1216
1227
|
break;
|
|
1217
1228
|
case 'fail-on-v1-path':
|
|
1218
1229
|
this.engine.failNextV1Path();
|
|
@@ -2097,7 +2108,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2097
2108
|
// find the participant
|
|
2098
2109
|
const participant = this.remoteParticipants.get(packet.participantIdentity);
|
|
2099
2110
|
if (packet.value.case === 'user') {
|
|
2100
|
-
this.handleUserPacket(
|
|
2111
|
+
this.handleUserPacket(
|
|
2112
|
+
participant,
|
|
2113
|
+
packet.value.value,
|
|
2114
|
+
packet.kind,
|
|
2115
|
+
encryptionType,
|
|
2116
|
+
packet.participantIdentity,
|
|
2117
|
+
);
|
|
2101
2118
|
} else if (packet.value.case === 'transcription') {
|
|
2102
2119
|
this.handleTranscription(participant, packet.value.value);
|
|
2103
2120
|
} else if (packet.value.case === 'sipDtmf') {
|
|
@@ -2147,6 +2164,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2147
2164
|
userPacket: UserPacket,
|
|
2148
2165
|
kind: DataPacket_Kind,
|
|
2149
2166
|
encryptionType: Encryption_Type,
|
|
2167
|
+
participantIdentity: string,
|
|
2150
2168
|
) => {
|
|
2151
2169
|
this.emit(
|
|
2152
2170
|
RoomEvent.DataReceived,
|
|
@@ -2155,6 +2173,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2155
2173
|
kind,
|
|
2156
2174
|
userPacket.topic,
|
|
2157
2175
|
encryptionType,
|
|
2176
|
+
participantIdentity,
|
|
2158
2177
|
);
|
|
2159
2178
|
|
|
2160
2179
|
// also emit on the participant
|
|
@@ -2566,7 +2585,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2566
2585
|
}
|
|
2567
2586
|
// Advertise deflate-raw decompression support so peers know they can send us compressed data
|
|
2568
2587
|
// streams (gated separately from clientProtocol — see the data streams v2 spec).
|
|
2569
|
-
if (
|
|
2588
|
+
if (isDeflateRawCompressionSupported()) {
|
|
2570
2589
|
capabilities.push(ClientInfo_Capability.CAP_COMPRESSION_DEFLATE_RAW);
|
|
2571
2590
|
}
|
|
2572
2591
|
return capabilities;
|
|
@@ -3060,6 +3079,7 @@ export type RoomEventCallbacks = {
|
|
|
3060
3079
|
kind?: DataPacket_Kind,
|
|
3061
3080
|
topic?: string,
|
|
3062
3081
|
encryptionType?: Encryption_Type,
|
|
3082
|
+
participantIdentity?: string,
|
|
3063
3083
|
) => void;
|
|
3064
3084
|
sipDTMFReceived: (dtmf: SipDTMF, participant?: RemoteParticipant) => void;
|
|
3065
3085
|
transcriptionReceived: (
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
Encryption_Type,
|
|
10
10
|
} from '@livekit/protocol';
|
|
11
11
|
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import {
|
|
13
|
+
stubMissingCompressionStreams,
|
|
14
|
+
stubMissingDeflateRawSupport,
|
|
15
|
+
} from '../../../test/compressionStreamStubs';
|
|
12
16
|
import { deflateRawCompress } from '../compression';
|
|
13
17
|
import { STREAM_CHUNK_SIZE_BYTES } from '../constants';
|
|
14
18
|
import IncomingDataStreamManager from './IncomingDataStreamManager';
|
|
@@ -907,18 +911,15 @@ describe('IncomingDataStreamManager', () => {
|
|
|
907
911
|
expect(await reader.readAll()).toStrictEqual(text);
|
|
908
912
|
});
|
|
909
913
|
|
|
910
|
-
it(
|
|
914
|
+
it.each([
|
|
915
|
+
[`DecompressionStream doesn't exist`, stubMissingCompressionStreams],
|
|
916
|
+
[`deflate-raw isn't a supported format`, stubMissingDeflateRawSupport],
|
|
917
|
+
])(`should ignore a v2 TEXT data stream with compression if %s`, async (_label, stub) => {
|
|
911
918
|
const text = 'hello world';
|
|
912
919
|
const compressed = await deflateRawCompress(new TextEncoder().encode(text));
|
|
913
920
|
|
|
914
|
-
|
|
915
|
-
originalDecompressionStream: typeof DecompressionStream;
|
|
921
|
+
const restoreCompressionStreams = stub();
|
|
916
922
|
try {
|
|
917
|
-
originalCompressionStream = CompressionStream;
|
|
918
|
-
(globalThis as any).CompressionStream = undefined;
|
|
919
|
-
originalDecompressionStream = DecompressionStream;
|
|
920
|
-
(globalThis as any).DecompressionStream = undefined;
|
|
921
|
-
|
|
922
923
|
const manager = new IncomingDataStreamManager();
|
|
923
924
|
manager.setConnected(true);
|
|
924
925
|
|
|
@@ -977,23 +978,19 @@ describe('IncomingDataStreamManager', () => {
|
|
|
977
978
|
Promise.race([readerPromise, Promise.resolve('still pending')]),
|
|
978
979
|
).resolves.toStrictEqual('still pending');
|
|
979
980
|
} finally {
|
|
980
|
-
(
|
|
981
|
-
(globalThis as any).DecompressionStream = originalDecompressionStream!;
|
|
981
|
+
restoreCompressionStreams();
|
|
982
982
|
}
|
|
983
983
|
});
|
|
984
984
|
|
|
985
|
-
it(
|
|
985
|
+
it.each([
|
|
986
|
+
[`DecompressionStream doesn't exist`, stubMissingCompressionStreams],
|
|
987
|
+
[`deflate-raw isn't a supported format`, stubMissingDeflateRawSupport],
|
|
988
|
+
])(`should ignore a v2 BYTES data stream with compression if %s`, async (_label, stub) => {
|
|
986
989
|
const bytes = new Uint8Array([0x01, 0x02, 0x03]);
|
|
987
990
|
const compressed = await deflateRawCompress(bytes);
|
|
988
991
|
|
|
989
|
-
|
|
990
|
-
originalDecompressionStream: typeof DecompressionStream;
|
|
992
|
+
const restoreCompressionStreams = stub();
|
|
991
993
|
try {
|
|
992
|
-
originalCompressionStream = CompressionStream;
|
|
993
|
-
(globalThis as any).CompressionStream = undefined;
|
|
994
|
-
originalDecompressionStream = DecompressionStream;
|
|
995
|
-
(globalThis as any).DecompressionStream = undefined;
|
|
996
|
-
|
|
997
994
|
const manager = new IncomingDataStreamManager();
|
|
998
995
|
manager.setConnected(true);
|
|
999
996
|
|
|
@@ -1052,8 +1049,7 @@ describe('IncomingDataStreamManager', () => {
|
|
|
1052
1049
|
Promise.race([readerPromise, Promise.resolve('still pending')]),
|
|
1053
1050
|
).resolves.toStrictEqual('still pending');
|
|
1054
1051
|
} finally {
|
|
1055
|
-
(
|
|
1056
|
-
(globalThis as any).DecompressionStream = originalDecompressionStream!;
|
|
1052
|
+
restoreCompressionStreams();
|
|
1057
1053
|
}
|
|
1058
1054
|
});
|
|
1059
1055
|
|
|
@@ -10,7 +10,7 @@ import log from '../../../logger';
|
|
|
10
10
|
import { type NonSharedUint8Array } from '../../../type-polyfills/non-shared-typed-arrays';
|
|
11
11
|
import { DataStreamError, DataStreamErrorReason } from '../../errors';
|
|
12
12
|
import { type ByteStreamInfo, type StreamController, type TextStreamInfo } from '../../types';
|
|
13
|
-
import { bigIntToNumber,
|
|
13
|
+
import { bigIntToNumber, isDeflateRawCompressionSupported, numberToBigInt } from '../../utils';
|
|
14
14
|
import { deflateRawDecompress, inflateRawTransform } from '../compression';
|
|
15
15
|
import { DEFAULT_MAX_PAYLOAD_BYTE_LENGTH } from '../constants';
|
|
16
16
|
import {
|
|
@@ -174,11 +174,11 @@ export default class IncomingDataStreamManager {
|
|
|
174
174
|
let compressed;
|
|
175
175
|
switch (streamHeader.compression) {
|
|
176
176
|
case DataStream_CompressionType.DEFLATE_RAW:
|
|
177
|
-
if (!
|
|
177
|
+
if (!isDeflateRawCompressionSupported()) {
|
|
178
178
|
// NOTE: this shouldn't really ever happen, if this warning is logged then the sender
|
|
179
179
|
// isn't properly abiding by the data streams v2 protocol.
|
|
180
180
|
log.warn(
|
|
181
|
-
`Data stream ${streamHeader.streamId} received with deflate-raw compression, but this browser does not
|
|
181
|
+
`Data stream ${streamHeader.streamId} received with deflate-raw compression, but this browser does not support deflate-raw decompression. Dropping...`,
|
|
182
182
|
);
|
|
183
183
|
return;
|
|
184
184
|
}
|
|
@@ -279,11 +279,11 @@ export default class IncomingDataStreamManager {
|
|
|
279
279
|
let compressed;
|
|
280
280
|
switch (streamHeader.compression) {
|
|
281
281
|
case DataStream_CompressionType.DEFLATE_RAW:
|
|
282
|
-
if (!
|
|
282
|
+
if (!isDeflateRawCompressionSupported()) {
|
|
283
283
|
// NOTE: this shouldn't really ever happen, if this warning is logged then the sender
|
|
284
284
|
// isn't properly abiding by the data streams v2 protocol.
|
|
285
285
|
log.warn(
|
|
286
|
-
`Data stream ${streamHeader.streamId} received with deflate-raw compression, but this browser does not
|
|
286
|
+
`Data stream ${streamHeader.streamId} received with deflate-raw compression, but this browser does not support deflate-raw decompression. Dropping...`,
|
|
287
287
|
);
|
|
288
288
|
return;
|
|
289
289
|
}
|
|
@@ -5,14 +5,19 @@ import {
|
|
|
5
5
|
DataStream_CompressionType,
|
|
6
6
|
type DataStream_TextHeader,
|
|
7
7
|
} from '@livekit/protocol';
|
|
8
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
9
9
|
import log from '../../../logger';
|
|
10
|
+
import {
|
|
11
|
+
stubMissingCompressionStreams,
|
|
12
|
+
stubMissingDeflateRawSupport,
|
|
13
|
+
} from '../../../test/compressionStreamStubs';
|
|
10
14
|
import {
|
|
11
15
|
CLIENT_PROTOCOL_DATA_STREAM_RPC,
|
|
12
16
|
CLIENT_PROTOCOL_DATA_STREAM_V2,
|
|
13
17
|
CLIENT_PROTOCOL_DEFAULT,
|
|
14
18
|
} from '../../../version';
|
|
15
19
|
import type RTCEngine from '../../RTCEngine';
|
|
20
|
+
import { STREAM_CHUNK_SIZE_BYTES } from '../constants';
|
|
16
21
|
import OutgoingDataStreamManager from './OutgoingDataStreamManager';
|
|
17
22
|
|
|
18
23
|
/** Builds a low quality random string of the given length. */
|
|
@@ -1195,7 +1200,7 @@ describe('OutgoingDataStreamManager', () => {
|
|
|
1195
1200
|
// The split backs off below the MTU rather than bisecting the emoji, so each chunk decodes
|
|
1196
1201
|
// independently.
|
|
1197
1202
|
expect(first.content).toHaveLength(14_999);
|
|
1198
|
-
const decode = (bytes: Uint8Array) => new TextDecoder('utf-8'
|
|
1203
|
+
const decode = (bytes: Uint8Array) => new TextDecoder('utf-8').decode(bytes);
|
|
1199
1204
|
expect(() => decode(first.content)).not.toThrow();
|
|
1200
1205
|
expect(() => decode(second.content)).not.toThrow();
|
|
1201
1206
|
expect(decode(first.content) + decode(second.content)).toStrictEqual(text);
|
|
@@ -1243,50 +1248,66 @@ describe('OutgoingDataStreamManager', () => {
|
|
|
1243
1248
|
});
|
|
1244
1249
|
});
|
|
1245
1250
|
|
|
1246
|
-
describe(
|
|
1251
|
+
describe.each([
|
|
1252
|
+
['a runtime without CompressionStream', stubMissingCompressionStreams],
|
|
1253
|
+
['a runtime without deflate-raw support', stubMissingDeflateRawSupport],
|
|
1254
|
+
])('on %s', (_label, stub) => {
|
|
1255
|
+
let restoreCompressionStreams: () => void;
|
|
1256
|
+
|
|
1257
|
+
beforeEach(() => {
|
|
1258
|
+
restoreCompressionStreams = stub();
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
afterEach(() => {
|
|
1262
|
+
restoreCompressionStreams();
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1247
1265
|
it('should send raw inline even to compression-capable recipients when the runtime cannot compress', async () => {
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
|
|
1266
|
+
const { manager, sentPackets } = createManager({ alice: CLIENT_PROTOCOL_DATA_STREAM_V2 });
|
|
1267
|
+
await manager.sendText('hello hello compressible world', {
|
|
1268
|
+
topic: 'my-topic',
|
|
1269
|
+
destinationIdentities: ['alice'],
|
|
1270
|
+
});
|
|
1251
1271
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1272
|
+
// Compression eligibility also requires a local compressor; without one, inline still
|
|
1273
|
+
// applies but the payload is sent raw.
|
|
1274
|
+
expect(sentPackets).toHaveLength(1);
|
|
1275
|
+
const header = headerOf(sentPackets[0]);
|
|
1276
|
+
expect(header.compression).toBe(DataStream_CompressionType.NONE);
|
|
1277
|
+
expect(header.inlineContent).toStrictEqual(
|
|
1278
|
+
new TextEncoder().encode('hello hello compressible world'),
|
|
1279
|
+
);
|
|
1280
|
+
});
|
|
1257
1281
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1282
|
+
it('should send BYTES uncompressed when the runtime cannot compress', async () => {
|
|
1283
|
+
const { manager, sentPackets } = createManager({ alice: CLIENT_PROTOCOL_DATA_STREAM_V2 });
|
|
1284
|
+
// Larger than the inline budget, so this takes the chunked path rather than riding along in
|
|
1285
|
+
// the header.
|
|
1286
|
+
const bytes = new Uint8Array(20_000).fill(0x07);
|
|
1287
|
+
await manager.sendBytes(bytes, {
|
|
1288
|
+
topic: 'my-topic',
|
|
1289
|
+
destinationIdentities: ['alice'],
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
expect(sentPackets).toHaveLength(4);
|
|
1293
|
+
const header = headerOf(sentPackets[0]);
|
|
1294
|
+
expect(header.compression).toBe(DataStream_CompressionType.NONE);
|
|
1295
|
+
expect(chunkOf(sentPackets[1]).content).toHaveLength(STREAM_CHUNK_SIZE_BYTES);
|
|
1296
|
+
expect(chunkOf(sentPackets[2]).content).toHaveLength(20_000 - STREAM_CHUNK_SIZE_BYTES);
|
|
1269
1297
|
});
|
|
1270
1298
|
|
|
1271
1299
|
it('should send a FILE uncompressed when the runtime cannot compress', async () => {
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
await manager.sendFile(new File([bytes as NonSharedUint8Array], 'text.txt'), {
|
|
1279
|
-
topic: 'my-topic',
|
|
1280
|
-
destinationIdentities: ['alice'],
|
|
1281
|
-
});
|
|
1300
|
+
const { manager, sentPackets } = createManager({ alice: CLIENT_PROTOCOL_DATA_STREAM_V2 });
|
|
1301
|
+
const bytes = new Uint8Array(10_000).fill(0x07);
|
|
1302
|
+
await manager.sendFile(new File([bytes as NonSharedUint8Array], 'text.txt'), {
|
|
1303
|
+
topic: 'my-topic',
|
|
1304
|
+
destinationIdentities: ['alice'],
|
|
1305
|
+
});
|
|
1282
1306
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
} finally {
|
|
1288
|
-
(globalThis as any).CompressionStream = originalCompressionStream;
|
|
1289
|
-
}
|
|
1307
|
+
expect(sentPackets).toHaveLength(3);
|
|
1308
|
+
const header = headerOf(sentPackets[0]);
|
|
1309
|
+
expect(header.compression).toBe(DataStream_CompressionType.NONE);
|
|
1310
|
+
expect(chunkOf(sentPackets[1]).content).toHaveLength(10_000);
|
|
1290
1311
|
});
|
|
1291
1312
|
});
|
|
1292
1313
|
});
|
|
@@ -24,7 +24,7 @@ import type {
|
|
|
24
24
|
TextStreamInfo,
|
|
25
25
|
} from '../../types';
|
|
26
26
|
import {
|
|
27
|
-
|
|
27
|
+
isDeflateRawCompressionSupported,
|
|
28
28
|
numberToBigInt,
|
|
29
29
|
readBytesInChunks,
|
|
30
30
|
readableFromBytes,
|
|
@@ -101,7 +101,7 @@ export default class OutgoingDataStreamManager {
|
|
|
101
101
|
|
|
102
102
|
const compressEligible =
|
|
103
103
|
compress &&
|
|
104
|
-
|
|
104
|
+
isDeflateRawCompressionSupported() &&
|
|
105
105
|
this.allRecipientsSupportV2(options?.destinationIdentities) &&
|
|
106
106
|
this.allRecipientsSupportCompression(options?.destinationIdentities);
|
|
107
107
|
let compressedStream = compressEligible
|
|
@@ -239,7 +239,7 @@ export default class OutgoingDataStreamManager {
|
|
|
239
239
|
|
|
240
240
|
const compressEligible =
|
|
241
241
|
compress &&
|
|
242
|
-
|
|
242
|
+
isDeflateRawCompressionSupported() &&
|
|
243
243
|
this.allRecipientsSupportV2(destinationIdentities) &&
|
|
244
244
|
this.allRecipientsSupportCompression(destinationIdentities);
|
|
245
245
|
let compressedStream = compressEligible
|
|
@@ -463,7 +463,7 @@ export default class OutgoingDataStreamManager {
|
|
|
463
463
|
const destinationIdentities = options?.destinationIdentities;
|
|
464
464
|
const compress =
|
|
465
465
|
(options?.compress ?? true) &&
|
|
466
|
-
|
|
466
|
+
isDeflateRawCompressionSupported() &&
|
|
467
467
|
this.allRecipientsSupportV2(destinationIdentities) &&
|
|
468
468
|
this.allRecipientsSupportCompression(destinationIdentities);
|
|
469
469
|
|
package/src/room/defaults.ts
CHANGED
|
@@ -47,6 +47,7 @@ export const roomOptionDefaults: InternalRoomOptions = {
|
|
|
47
47
|
|
|
48
48
|
export const roomConnectOptionDefaults: InternalRoomConnectOptions = {
|
|
49
49
|
autoSubscribe: true,
|
|
50
|
+
disableIceLite: false,
|
|
50
51
|
maxRetries: 1,
|
|
51
52
|
peerConnectionTimeout: 15_000,
|
|
52
53
|
websocketTimeout: 15_000,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ConnectionError, canFailOverToAnotherRegion } from './errors';
|
|
3
|
+
|
|
4
|
+
describe('canFailOverToAnotherRegion', () => {
|
|
5
|
+
it('allows failover on 403, which LiveKit Cloud uses to signal region pinning', () => {
|
|
6
|
+
// The RTC paths 403 when the project is not allowed in the region the client geo-routed to.
|
|
7
|
+
// `/settings/regions` stays reachable so the client can discover where it is allowed.
|
|
8
|
+
expect(
|
|
9
|
+
canFailOverToAnotherRegion(
|
|
10
|
+
ConnectionError.notAllowed('project not allowed in this region.', 403),
|
|
11
|
+
),
|
|
12
|
+
).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('does not allow failover on 401 — no other region will accept the same token', () => {
|
|
16
|
+
expect(canFailOverToAnotherRegion(ConnectionError.notAllowed('unauthorized', 401))).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('does not allow failover when the room does not exist, reported as NotAllowed with 404', () => {
|
|
20
|
+
expect(
|
|
21
|
+
canFailOverToAnotherRegion(ConnectionError.notAllowed('requested room does not exist', 404)),
|
|
22
|
+
).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('does not allow failover on a cancelled attempt', () => {
|
|
26
|
+
expect(canFailOverToAnotherRegion(ConnectionError.cancelled('aborted'))).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it.each([
|
|
30
|
+
['server unreachable', ConnectionError.serverUnreachable('unreachable')],
|
|
31
|
+
['timeout', ConnectionError.timeout('timed out')],
|
|
32
|
+
['internal', ConnectionError.internal('internal')],
|
|
33
|
+
])('allows failover on a %s error', (_name, error) => {
|
|
34
|
+
expect(canFailOverToAnotherRegion(error)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
});
|
package/src/room/errors.ts
CHANGED
|
@@ -188,6 +188,33 @@ export class ConnectionError<
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Whether a failed connection attempt may be retried against a different LiveKit Cloud region.
|
|
193
|
+
*
|
|
194
|
+
* LiveKit Cloud signals project-level region pinning by returning 403 on the RTC paths when the
|
|
195
|
+
* project is not allowed in the region the client geo-routed to. `/settings/regions` is
|
|
196
|
+
* deliberately left reachable so the client can discover its allowed regions and connect there, so
|
|
197
|
+
* a 403 must not be treated as terminal.
|
|
198
|
+
*
|
|
199
|
+
* A 401 stays terminal: no other region will accept a token this one rejected. So does the 404
|
|
200
|
+
* "requested room does not exist" case, which is also reported as `NotAllowed`.
|
|
201
|
+
*
|
|
202
|
+
* We key on the status rather than the server's error message because that message is an
|
|
203
|
+
* unversioned human-readable string; matching it would let a copy edit break already-shipped
|
|
204
|
+
* clients. If a 403 really was a permissions failure rather than region pinning, every region
|
|
205
|
+
* attempt fails the same way and the original error still surfaces — at the cost of one extra
|
|
206
|
+
* region lookup.
|
|
207
|
+
*/
|
|
208
|
+
export function canFailOverToAnotherRegion(error: ConnectionError): boolean {
|
|
209
|
+
if (error.reason === ConnectionErrorReason.Cancelled) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
if (error.reason === ConnectionErrorReason.NotAllowed) {
|
|
213
|
+
return error.status === 403;
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
191
218
|
export class DeviceUnsupportedError extends LivekitError {
|
|
192
219
|
readonly name = 'DeviceUnsupportedError';
|
|
193
220
|
|
package/src/room/events.ts
CHANGED
|
@@ -225,7 +225,7 @@ export enum RoomEvent {
|
|
|
225
225
|
* Data packets provides the ability to use LiveKit to send/receive arbitrary payloads.
|
|
226
226
|
* All participants in the room will receive the messages sent to the room.
|
|
227
227
|
*
|
|
228
|
-
* args: (payload: NonSharedUint8Array, participant: [[Participant]], kind: [[DataPacket_Kind]], topic?: string)
|
|
228
|
+
* args: (payload: NonSharedUint8Array, participant: [[Participant]] | undefined, kind: [[DataPacket_Kind]], topic?: string, encryptionType?: Encryption_Type, participantIdentity?: string)
|
|
229
229
|
*/
|
|
230
230
|
DataReceived = 'dataReceived',
|
|
231
231
|
|
|
@@ -633,6 +633,7 @@ export enum EngineEvent {
|
|
|
633
633
|
Joined = 'joined',
|
|
634
634
|
TokenRefreshed = 'tokenRefreshed',
|
|
635
635
|
ServerRegionsReported = 'serverRegionsReported',
|
|
636
|
+
RequestSubscribedCodecRefresh = 'requestSubscribedCodecRefresh',
|
|
636
637
|
}
|
|
637
638
|
|
|
638
639
|
export enum TrackEvent {
|
|
@@ -74,25 +74,6 @@ export const computeDefaultScreenShareSimulcastPresets = (fromPreset: VideoPrese
|
|
|
74
74
|
);
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
-
// /**
|
|
78
|
-
// *
|
|
79
|
-
// * @internal
|
|
80
|
-
// * @experimental
|
|
81
|
-
// */
|
|
82
|
-
// const computeDefaultMultiCodecSimulcastEncodings = (width: number, height: number) => {
|
|
83
|
-
// // use vp8 as a default
|
|
84
|
-
// const vp8 = determineAppropriateEncoding(false, width, height);
|
|
85
|
-
// const vp9 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.9 };
|
|
86
|
-
// const h264 = { ...vp8, maxBitrate: vp8.maxBitrate * 1.1 };
|
|
87
|
-
// const av1 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.7 };
|
|
88
|
-
// return {
|
|
89
|
-
// vp8,
|
|
90
|
-
// vp9,
|
|
91
|
-
// h264,
|
|
92
|
-
// av1,
|
|
93
|
-
// };
|
|
94
|
-
// };
|
|
95
|
-
|
|
96
77
|
const videoRids = ['q', 'h', 'f'];
|
|
97
78
|
|
|
98
79
|
/* @internal */
|