livekit-client 2.22.3 → 2.23.0
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 +71 -8
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +1746 -547
- 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/api/WebSocketStream.d.ts +1 -0
- package/dist/src/api/WebSocketStream.d.ts.map +1 -1
- package/dist/src/api/utils.d.ts +1 -1
- package/dist/src/api/utils.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +12 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.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/DefaultReconnectPolicy.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +100 -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 +16 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +54 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-channel/DataChannelManager.d.ts +9 -8
- package/dist/src/room/data-channel/DataChannelManager.d.ts.map +1 -1
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts +25 -12
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts.map +1 -1
- package/dist/src/room/data-channel/LossyDataChannel.d.ts.map +1 -1
- package/dist/src/room/data-channel/ReliableDataChannel.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.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/LocalParticipant.d.ts +4 -0
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +1 -1
- package/dist/src/room/participant/Participant.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/rpc/utils.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +24 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +18 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +9 -0
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts +6 -0
- package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/processor/types.d.ts +11 -0
- package/dist/src/room/track/processor/types.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 +60 -1
- 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 +34 -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 +39 -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 +268 -175
- package/dist/ts4.2/room/PCTransportManager.d.ts +77 -77
- package/dist/ts4.2/room/RTCEngine.d.ts +277 -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 +407 -365
- package/dist/ts4.2/room/attribute-typings.d.ts +35 -35
- package/dist/ts4.2/room/data-channel/DataChannelManager.d.ts +63 -62
- package/dist/ts4.2/room/data-channel/FlowControlledDataChannel.d.ts +107 -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 +291 -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 +62 -42
- package/dist/ts4.2/room/track/LocalTrack.d.ts +143 -125
- package/dist/ts4.2/room/track/LocalTrackPublication.d.ts +42 -42
- package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +109 -100
- package/dist/ts4.2/room/track/RemoteAudioTrack.d.ts +61 -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 +42 -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 +273 -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 +394 -10
- package/src/api/SignalClient.ts +132 -76
- package/src/api/WebSocketStream.test.ts +11 -0
- package/src/api/WebSocketStream.ts +22 -1
- package/src/api/utils.ts +1 -1
- package/src/e2ee/E2eeManager.test.ts +43 -0
- package/src/e2ee/E2eeManager.ts +6 -2
- package/src/e2ee/worker/FrameCryptor.test.ts +212 -90
- package/src/e2ee/worker/FrameCryptor.ts +21 -4
- package/src/e2ee/worker/naluUtils.test.ts +123 -0
- package/src/e2ee/worker/naluUtils.ts +57 -2
- package/src/index.ts +2 -0
- package/src/options.ts +16 -0
- package/src/room/DefaultReconnectPolicy.test.ts +58 -0
- package/src/room/DefaultReconnectPolicy.ts +14 -2
- package/src/room/PCTransport.test.ts +327 -9
- package/src/room/PCTransport.ts +339 -36
- package/src/room/PCTransportManager.ts +3 -1
- package/src/room/RTCEngine.test.ts +318 -2
- package/src/room/RTCEngine.ts +126 -12
- package/src/room/Room.test.ts +148 -6
- package/src/room/Room.ts +312 -80
- package/src/room/data-channel/DataChannelManager.test.ts +24 -7
- package/src/room/data-channel/DataChannelManager.ts +43 -38
- package/src/room/data-channel/FlowControlledDataChannel.test.ts +60 -18
- package/src/room/data-channel/FlowControlledDataChannel.ts +40 -22
- package/src/room/data-channel/LossyDataChannel.test.ts +38 -0
- package/src/room/data-channel/LossyDataChannel.ts +10 -1
- package/src/room/data-channel/ReliableDataChannel.test.ts +72 -0
- package/src/room/data-channel/ReliableDataChannel.ts +16 -5
- package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +47 -20
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +16 -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/LocalParticipant.test.ts +136 -1
- package/src/room/participant/LocalParticipant.ts +66 -17
- package/src/room/participant/Participant.ts +6 -3
- package/src/room/participant/publishUtils.ts +0 -19
- package/src/room/participant/restartOnDefaultAudioDevice.test.ts +177 -0
- package/src/room/rpc/client/RpcClientManager.test.ts +51 -0
- package/src/room/rpc/client/RpcClientManager.ts +32 -15
- package/src/room/rpc/utils.test.ts +32 -0
- package/src/room/rpc/utils.ts +7 -0
- package/src/room/track/LocalAudioTrack.ts +101 -10
- package/src/room/track/LocalTrack.ts +87 -19
- package/src/room/track/LocalVideoTrack.ts +17 -3
- package/src/room/track/RemoteAudioTrack.ts +59 -14
- package/src/room/track/RemoteVideoTrack.test.ts +210 -0
- package/src/room/track/RemoteVideoTrack.ts +29 -3
- package/src/room/track/processor/types.ts +11 -0
- package/src/room/track/utils.test.ts +206 -2
- package/src/room/track/utils.ts +117 -5
- package/src/room/utils.test.ts +250 -0
- package/src/room/utils.ts +152 -19
- package/src/test/compressionStreamStubs.ts +53 -0
|
@@ -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';
|
|
@@ -675,6 +679,37 @@ describe('IncomingDataStreamManager', () => {
|
|
|
675
679
|
'Participant alice unexpectedly disconnected in the middle of sending data',
|
|
676
680
|
);
|
|
677
681
|
});
|
|
682
|
+
|
|
683
|
+
it('should error in flight streams when the controllers are cleared on room disconnect', async () => {
|
|
684
|
+
const manager = new IncomingDataStreamManager();
|
|
685
|
+
manager.setConnected(true);
|
|
686
|
+
|
|
687
|
+
const textReaderPromise = new Promise<TextStreamReader>((resolve) => {
|
|
688
|
+
manager.registerTextStreamHandler('my-topic', (reader) => resolve(reader));
|
|
689
|
+
});
|
|
690
|
+
const byteReaderPromise = new Promise<ByteStreamReader>((resolve) => {
|
|
691
|
+
manager.registerByteStreamHandler('my-topic', (reader) => resolve(reader));
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
manager.handleDataStreamPacket(headerPacket('text-1', 'textHeader'), Encryption_Type.NONE);
|
|
695
|
+
manager.handleDataStreamPacket(
|
|
696
|
+
chunkPacket('text-1', 0, new Uint8Array([0x68, 0x69])),
|
|
697
|
+
Encryption_Type.NONE,
|
|
698
|
+
);
|
|
699
|
+
manager.handleDataStreamPacket(headerPacket('byte-1', 'byteHeader'), Encryption_Type.NONE);
|
|
700
|
+
manager.handleDataStreamPacket(
|
|
701
|
+
chunkPacket('byte-1', 0, new Uint8Array([0x01])),
|
|
702
|
+
Encryption_Type.NONE,
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
// Room#handleDisconnect calls this while the streams are still open
|
|
706
|
+
manager.clearControllers();
|
|
707
|
+
|
|
708
|
+
const textReader = await textReaderPromise;
|
|
709
|
+
const byteReader = await byteReaderPromise;
|
|
710
|
+
await expect(textReader.readAll()).rejects.toThrow('Room disconnected');
|
|
711
|
+
await expect(byteReader.readAll()).rejects.toThrow('Room disconnected');
|
|
712
|
+
});
|
|
678
713
|
});
|
|
679
714
|
|
|
680
715
|
describe('Receiving v2 data streams', () => {
|
|
@@ -907,18 +942,15 @@ describe('IncomingDataStreamManager', () => {
|
|
|
907
942
|
expect(await reader.readAll()).toStrictEqual(text);
|
|
908
943
|
});
|
|
909
944
|
|
|
910
|
-
it(
|
|
945
|
+
it.each([
|
|
946
|
+
[`DecompressionStream doesn't exist`, stubMissingCompressionStreams],
|
|
947
|
+
[`deflate-raw isn't a supported format`, stubMissingDeflateRawSupport],
|
|
948
|
+
])(`should ignore a v2 TEXT data stream with compression if %s`, async (_label, stub) => {
|
|
911
949
|
const text = 'hello world';
|
|
912
950
|
const compressed = await deflateRawCompress(new TextEncoder().encode(text));
|
|
913
951
|
|
|
914
|
-
|
|
915
|
-
originalDecompressionStream: typeof DecompressionStream;
|
|
952
|
+
const restoreCompressionStreams = stub();
|
|
916
953
|
try {
|
|
917
|
-
originalCompressionStream = CompressionStream;
|
|
918
|
-
(globalThis as any).CompressionStream = undefined;
|
|
919
|
-
originalDecompressionStream = DecompressionStream;
|
|
920
|
-
(globalThis as any).DecompressionStream = undefined;
|
|
921
|
-
|
|
922
954
|
const manager = new IncomingDataStreamManager();
|
|
923
955
|
manager.setConnected(true);
|
|
924
956
|
|
|
@@ -977,23 +1009,19 @@ describe('IncomingDataStreamManager', () => {
|
|
|
977
1009
|
Promise.race([readerPromise, Promise.resolve('still pending')]),
|
|
978
1010
|
).resolves.toStrictEqual('still pending');
|
|
979
1011
|
} finally {
|
|
980
|
-
(
|
|
981
|
-
(globalThis as any).DecompressionStream = originalDecompressionStream!;
|
|
1012
|
+
restoreCompressionStreams();
|
|
982
1013
|
}
|
|
983
1014
|
});
|
|
984
1015
|
|
|
985
|
-
it(
|
|
1016
|
+
it.each([
|
|
1017
|
+
[`DecompressionStream doesn't exist`, stubMissingCompressionStreams],
|
|
1018
|
+
[`deflate-raw isn't a supported format`, stubMissingDeflateRawSupport],
|
|
1019
|
+
])(`should ignore a v2 BYTES data stream with compression if %s`, async (_label, stub) => {
|
|
986
1020
|
const bytes = new Uint8Array([0x01, 0x02, 0x03]);
|
|
987
1021
|
const compressed = await deflateRawCompress(bytes);
|
|
988
1022
|
|
|
989
|
-
|
|
990
|
-
originalDecompressionStream: typeof DecompressionStream;
|
|
1023
|
+
const restoreCompressionStreams = stub();
|
|
991
1024
|
try {
|
|
992
|
-
originalCompressionStream = CompressionStream;
|
|
993
|
-
(globalThis as any).CompressionStream = undefined;
|
|
994
|
-
originalDecompressionStream = DecompressionStream;
|
|
995
|
-
(globalThis as any).DecompressionStream = undefined;
|
|
996
|
-
|
|
997
1025
|
const manager = new IncomingDataStreamManager();
|
|
998
1026
|
manager.setConnected(true);
|
|
999
1027
|
|
|
@@ -1052,8 +1080,7 @@ describe('IncomingDataStreamManager', () => {
|
|
|
1052
1080
|
Promise.race([readerPromise, Promise.resolve('still pending')]),
|
|
1053
1081
|
).resolves.toStrictEqual('still pending');
|
|
1054
1082
|
} finally {
|
|
1055
|
-
(
|
|
1056
|
-
(globalThis as any).DecompressionStream = originalDecompressionStream!;
|
|
1083
|
+
restoreCompressionStreams();
|
|
1057
1084
|
}
|
|
1058
1085
|
});
|
|
1059
1086
|
|
|
@@ -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 {
|
|
@@ -87,6 +87,17 @@ export default class IncomingDataStreamManager {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
clearControllers() {
|
|
90
|
+
// Error any in flight streams so readers do not wait forever for chunks that can no longer arrive.
|
|
91
|
+
const abnormalEndError = new DataStreamError(
|
|
92
|
+
'Room disconnected in the middle of receiving data',
|
|
93
|
+
DataStreamErrorReason.AbnormalEnd,
|
|
94
|
+
);
|
|
95
|
+
for (const { controller } of this.byteStreamControllers.values()) {
|
|
96
|
+
controller.error(abnormalEndError);
|
|
97
|
+
}
|
|
98
|
+
for (const { controller } of this.textStreamControllers.values()) {
|
|
99
|
+
controller.error(abnormalEndError);
|
|
100
|
+
}
|
|
90
101
|
this.byteStreamControllers.clear();
|
|
91
102
|
this.textStreamControllers.clear();
|
|
92
103
|
this.bufferedPackets = [];
|
|
@@ -174,11 +185,11 @@ export default class IncomingDataStreamManager {
|
|
|
174
185
|
let compressed;
|
|
175
186
|
switch (streamHeader.compression) {
|
|
176
187
|
case DataStream_CompressionType.DEFLATE_RAW:
|
|
177
|
-
if (!
|
|
188
|
+
if (!isDeflateRawCompressionSupported()) {
|
|
178
189
|
// NOTE: this shouldn't really ever happen, if this warning is logged then the sender
|
|
179
190
|
// isn't properly abiding by the data streams v2 protocol.
|
|
180
191
|
log.warn(
|
|
181
|
-
`Data stream ${streamHeader.streamId} received with deflate-raw compression, but this browser does not
|
|
192
|
+
`Data stream ${streamHeader.streamId} received with deflate-raw compression, but this browser does not support deflate-raw decompression. Dropping...`,
|
|
182
193
|
);
|
|
183
194
|
return;
|
|
184
195
|
}
|
|
@@ -279,11 +290,11 @@ export default class IncomingDataStreamManager {
|
|
|
279
290
|
let compressed;
|
|
280
291
|
switch (streamHeader.compression) {
|
|
281
292
|
case DataStream_CompressionType.DEFLATE_RAW:
|
|
282
|
-
if (!
|
|
293
|
+
if (!isDeflateRawCompressionSupported()) {
|
|
283
294
|
// NOTE: this shouldn't really ever happen, if this warning is logged then the sender
|
|
284
295
|
// isn't properly abiding by the data streams v2 protocol.
|
|
285
296
|
log.warn(
|
|
286
|
-
`Data stream ${streamHeader.streamId} received with deflate-raw compression, but this browser does not
|
|
297
|
+
`Data stream ${streamHeader.streamId} received with deflate-raw compression, but this browser does not support deflate-raw decompression. Dropping...`,
|
|
287
298
|
);
|
|
288
299
|
return;
|
|
289
300
|
}
|
|
@@ -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 {
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type AddTrackRequest,
|
|
3
|
+
Encryption_Type,
|
|
4
|
+
PacketTrailerFeature,
|
|
5
|
+
VideoQuality,
|
|
6
|
+
} from '@livekit/protocol';
|
|
2
7
|
import { describe, expect, it, vi } from 'vitest';
|
|
3
8
|
import type LocalTrack from '../track/LocalTrack';
|
|
9
|
+
import type LocalVideoTrack from '../track/LocalVideoTrack';
|
|
4
10
|
import { Track } from '../track/Track';
|
|
5
11
|
import type { TrackPublishOptions } from '../track/options';
|
|
6
12
|
import LocalParticipant from './LocalParticipant';
|
|
@@ -79,3 +85,132 @@ describe('LocalParticipant frame metadata publish options', () => {
|
|
|
79
85
|
expect(participant.log.warn).toHaveBeenCalledOnce();
|
|
80
86
|
});
|
|
81
87
|
});
|
|
88
|
+
|
|
89
|
+
type BackupCodecTestParticipant = {
|
|
90
|
+
encryptionType: Encryption_Type;
|
|
91
|
+
trackPublications: Map<string, { track: LocalVideoTrack }>;
|
|
92
|
+
roomOptions: { publishDefaults: TrackPublishOptions };
|
|
93
|
+
engine: {
|
|
94
|
+
isClosed: boolean;
|
|
95
|
+
addTrack: ReturnType<typeof vi.fn>;
|
|
96
|
+
createSimulcastSender: ReturnType<typeof vi.fn>;
|
|
97
|
+
negotiate: ReturnType<typeof vi.fn>;
|
|
98
|
+
};
|
|
99
|
+
log: { warn: ReturnType<typeof vi.fn>; debug: ReturnType<typeof vi.fn> };
|
|
100
|
+
publishAdditionalCodecForTrack: LocalParticipant['publishAdditionalCodecForTrack'];
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// a resizing processor makes mediaStreamTrack (processed) and dimensions (raw capture) differ
|
|
104
|
+
function makeBackupCodecTrack(
|
|
105
|
+
settings: MediaTrackSettings,
|
|
106
|
+
source = Track.Source.Camera,
|
|
107
|
+
captureSettings = settings,
|
|
108
|
+
) {
|
|
109
|
+
return {
|
|
110
|
+
isLocal: true,
|
|
111
|
+
kind: Track.Kind.Video,
|
|
112
|
+
sid: 'TR_video',
|
|
113
|
+
source,
|
|
114
|
+
isMuted: false,
|
|
115
|
+
mediaStreamID: 'stream-id',
|
|
116
|
+
mediaStreamTrack: { enabled: true, id: 'primary-cid', getSettings: () => settings },
|
|
117
|
+
get dimensions() {
|
|
118
|
+
const { width, height } = captureSettings;
|
|
119
|
+
return width && height ? { width, height } : undefined;
|
|
120
|
+
},
|
|
121
|
+
addSimulcastTrack: () => ({ mediaStreamTrack: { id: 'backup-cid' } }),
|
|
122
|
+
} as unknown as LocalVideoTrack;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function makeBackupCodecParticipant(track: LocalVideoTrack) {
|
|
126
|
+
const participant = Object.create(LocalParticipant.prototype) as BackupCodecTestParticipant;
|
|
127
|
+
// Object.create runs no field initializers, so without this the encryption guard sees
|
|
128
|
+
// undefined and the method returns before publishing anything.
|
|
129
|
+
participant.encryptionType = Encryption_Type.NONE;
|
|
130
|
+
participant.trackPublications = new Map([['TR_video', { track }]]);
|
|
131
|
+
participant.roomOptions = {
|
|
132
|
+
publishDefaults: { videoCodec: 'vp9', backupCodec: { codec: 'vp8' }, simulcast: true },
|
|
133
|
+
};
|
|
134
|
+
participant.engine = {
|
|
135
|
+
isClosed: false,
|
|
136
|
+
addTrack: vi.fn(async () => ({})),
|
|
137
|
+
createSimulcastSender: vi.fn(async () => {}),
|
|
138
|
+
negotiate: vi.fn(async () => {}),
|
|
139
|
+
};
|
|
140
|
+
participant.log = { warn: vi.fn(), debug: vi.fn() };
|
|
141
|
+
return participant;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function capturedRequest(participant: BackupCodecTestParticipant): AddTrackRequest {
|
|
145
|
+
expect(participant.engine.addTrack).toHaveBeenCalledOnce();
|
|
146
|
+
return participant.engine.addTrack.mock.calls[0][0] as AddTrackRequest;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
describe('LocalParticipant.publishAdditionalCodecForTrack', () => {
|
|
150
|
+
it('sets the capture dimensions and the backup codec layers', async () => {
|
|
151
|
+
const track = makeBackupCodecTrack({ width: 1280, height: 720 });
|
|
152
|
+
const participant = makeBackupCodecParticipant(track);
|
|
153
|
+
|
|
154
|
+
await participant.publishAdditionalCodecForTrack(track, 'vp8');
|
|
155
|
+
|
|
156
|
+
const req = capturedRequest(participant);
|
|
157
|
+
expect(req.width).toBe(1280);
|
|
158
|
+
expect(req.height).toBe(720);
|
|
159
|
+
expect(req.layers.length).toBeGreaterThan(0);
|
|
160
|
+
for (const layer of req.layers) {
|
|
161
|
+
expect(layer.width).toBeGreaterThan(0);
|
|
162
|
+
expect(layer.height).toBeGreaterThan(0);
|
|
163
|
+
}
|
|
164
|
+
expect(Math.max(...req.layers.map((layer) => layer.width))).toBe(1280);
|
|
165
|
+
expect(req.simulcastCodecs[0].codec).toBe('vp8');
|
|
166
|
+
expect(req.simulcastCodecs[0].layers).toEqual(req.layers);
|
|
167
|
+
expect(participant.log.warn).not.toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// screenshare disables simulcast for the backup codec, which collapses the encodings to a
|
|
171
|
+
// single unconstrained one and so to a single full size layer
|
|
172
|
+
it('sends one full size layer for a screenshare track', async () => {
|
|
173
|
+
const track = makeBackupCodecTrack({ width: 1280, height: 720 }, Track.Source.ScreenShare);
|
|
174
|
+
const participant = makeBackupCodecParticipant(track);
|
|
175
|
+
|
|
176
|
+
await participant.publishAdditionalCodecForTrack(track, 'vp8');
|
|
177
|
+
|
|
178
|
+
const req = capturedRequest(participant);
|
|
179
|
+
expect(req.layers).toHaveLength(1);
|
|
180
|
+
expect(req.layers[0].width).toBe(1280);
|
|
181
|
+
expect(req.layers[0].height).toBe(720);
|
|
182
|
+
expect(req.layers[0].quality).toBe(VideoQuality.HIGH);
|
|
183
|
+
expect(req.simulcastCodecs[0].layers).toEqual(req.layers);
|
|
184
|
+
expect(participant.log.warn).not.toHaveBeenCalled();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('sizes layers from the processed track when a processor resizes', async () => {
|
|
188
|
+
const track = makeBackupCodecTrack({ width: 640, height: 360 }, Track.Source.Camera, {
|
|
189
|
+
width: 1280,
|
|
190
|
+
height: 720,
|
|
191
|
+
});
|
|
192
|
+
const participant = makeBackupCodecParticipant(track);
|
|
193
|
+
|
|
194
|
+
await participant.publishAdditionalCodecForTrack(track, 'vp8');
|
|
195
|
+
|
|
196
|
+
const req = capturedRequest(participant);
|
|
197
|
+
expect(req.width).toBe(640);
|
|
198
|
+
expect(req.height).toBe(360);
|
|
199
|
+
expect(Math.max(...req.layers.map((layer) => layer.width))).toBe(640);
|
|
200
|
+
expect(req.simulcastCodecs[0].layers).toEqual(req.layers);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('omits layers when the capture dimensions are unknown', async () => {
|
|
204
|
+
const track = makeBackupCodecTrack({});
|
|
205
|
+
const participant = makeBackupCodecParticipant(track);
|
|
206
|
+
|
|
207
|
+
await participant.publishAdditionalCodecForTrack(track, 'vp8');
|
|
208
|
+
|
|
209
|
+
const req = capturedRequest(participant);
|
|
210
|
+
expect(req.width).toBe(0);
|
|
211
|
+
expect(req.height).toBe(0);
|
|
212
|
+
expect(req.layers).toEqual([]);
|
|
213
|
+
expect(req.simulcastCodecs[0].layers).toEqual([]);
|
|
214
|
+
expect(participant.log.warn).toHaveBeenCalledOnce();
|
|
215
|
+
});
|
|
216
|
+
});
|