livekit-client 2.19.2 → 2.20.1
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 +31 -27
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +334 -126
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.fm.worker.js +2 -0
- package/dist/{livekit-client.pt.worker.js.map → livekit-client.fm.worker.js.map} +1 -1
- package/dist/{livekit-client.pt.worker.mjs → livekit-client.fm.worker.mjs} +12 -4
- package/dist/livekit-client.fm.worker.mjs.map +1 -0
- 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/e2ee/E2eeManager.d.ts +2 -1
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +6 -5
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +1 -0
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts +1 -0
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +9 -8
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +1 -0
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/sifPayload.d.ts +1 -0
- package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
- package/dist/src/{packetTrailer/PacketTrailerManager.d.ts → frameMetadata/FrameMetadataManager.d.ts} +8 -4
- package/dist/src/{packetTrailer/PacketTrailerManager.d.ts.map → frameMetadata/FrameMetadataManager.d.ts.map} +1 -1
- package/dist/src/{packetTrailer/packetTrailer.d.ts → frameMetadata/frameMetadata.d.ts} +8 -7
- package/dist/src/frameMetadata/frameMetadata.d.ts.map +1 -0
- package/dist/src/{packetTrailer → frameMetadata}/types.d.ts +11 -6
- package/dist/src/frameMetadata/types.d.ts.map +1 -0
- package/dist/src/frameMetadata/utils.d.ts +9 -0
- package/dist/src/{packetTrailer → frameMetadata}/utils.d.ts.map +1 -1
- package/dist/src/frameMetadata/worker/frameMetadata.worker.d.ts +2 -0
- package/dist/src/frameMetadata/worker/frameMetadata.worker.d.ts.map +1 -0
- package/dist/src/index.d.ts +10 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/options.d.ts +8 -4
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +41 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +1 -0
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +4 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +3 -2
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
- package/dist/src/room/data-track/frame.d.ts +1 -0
- package/dist/src/room/data-track/frame.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/types.d.ts +1 -0
- package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/extensions.d.ts +1 -0
- package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/index.d.ts +1 -0
- package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/serializable.d.ts +1 -0
- package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +3 -2
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +1 -0
- 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/token-source/TokenSource.d.ts.map +1 -1
- package/dist/src/room/token-source/types.d.ts +1 -0
- package/dist/src/room/token-source/types.d.ts.map +1 -1
- package/dist/src/room/token-source/utils.d.ts.map +1 -1
- package/dist/src/room/track/FrameMetadataExtractor.d.ts +19 -0
- package/dist/src/room/track/FrameMetadataExtractor.d.ts.map +1 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts +7 -7
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +1 -0
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +17 -5
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/record.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +1 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/type-polyfills/non-shared-typed-arrays.d.ts +2 -0
- package/dist/src/type-polyfills/non-shared-typed-arrays.d.ts.map +1 -0
- package/dist/src/utils/dataPacketBuffer.d.ts +1 -0
- package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +2 -1
- package/dist/ts4.2/e2ee/types.d.ts +6 -5
- package/dist/ts4.2/e2ee/utils.d.ts +1 -0
- package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +1 -0
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +9 -8
- package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +1 -0
- package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +1 -0
- package/dist/ts4.2/{packetTrailer/PacketTrailerManager.d.ts → frameMetadata/FrameMetadataManager.d.ts} +8 -4
- package/dist/ts4.2/{packetTrailer/packetTrailer.d.ts → frameMetadata/frameMetadata.d.ts} +8 -7
- package/dist/ts4.2/{packetTrailer → frameMetadata}/types.d.ts +11 -6
- package/dist/ts4.2/frameMetadata/utils.d.ts +9 -0
- package/dist/ts4.2/frameMetadata/worker/frameMetadata.worker.d.ts +2 -0
- package/dist/ts4.2/index.d.ts +9 -3
- package/dist/ts4.2/options.d.ts +8 -4
- package/dist/ts4.2/room/PCTransport.d.ts +41 -1
- package/dist/ts4.2/room/PCTransportManager.d.ts +1 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +4 -1
- package/dist/ts4.2/room/Room.d.ts +3 -2
- package/dist/ts4.2/room/data-track/frame.d.ts +1 -0
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +1 -0
- package/dist/ts4.2/room/data-track/packet/extensions.d.ts +1 -0
- package/dist/ts4.2/room/data-track/packet/index.d.ts +1 -0
- package/dist/ts4.2/room/data-track/packet/serializable.d.ts +1 -0
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +3 -2
- package/dist/ts4.2/room/participant/Participant.d.ts +1 -0
- package/dist/ts4.2/room/token-source/types.d.ts +1 -0
- package/dist/ts4.2/room/track/FrameMetadataExtractor.d.ts +19 -0
- package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +7 -7
- package/dist/ts4.2/room/track/Track.d.ts +1 -0
- package/dist/ts4.2/room/track/options.d.ts +17 -5
- package/dist/ts4.2/room/utils.d.ts +1 -0
- package/dist/ts4.2/type-polyfills/non-shared-typed-arrays.d.ts +2 -0
- package/dist/ts4.2/utils/dataPacketBuffer.d.ts +1 -0
- package/package.json +24 -19
- package/src/api/SignalClient.test.ts +4 -4
- package/src/api/SignalClient.ts +8 -3
- package/src/api/WebSocketStream.test.ts +7 -7
- package/src/e2ee/E2eeManager.ts +16 -12
- package/src/e2ee/types.ts +6 -5
- package/src/e2ee/utils.ts +1 -0
- package/src/e2ee/worker/DataCryptor.ts +1 -0
- package/src/e2ee/worker/FrameCryptor.test.ts +6 -5
- package/src/e2ee/worker/FrameCryptor.ts +28 -23
- package/src/e2ee/worker/ParticipantKeyHandler.test.ts +1 -0
- package/src/e2ee/worker/e2ee.worker.ts +6 -5
- package/src/e2ee/worker/naluUtils.ts +1 -0
- package/src/e2ee/worker/sifPayload.ts +1 -0
- package/src/e2ee/worker/tsconfig.json +4 -1
- package/src/{packetTrailer/PacketTrailerManager.test.ts → frameMetadata/FrameMetadataManager.test.ts} +10 -8
- package/src/{packetTrailer/PacketTrailerManager.ts → frameMetadata/FrameMetadataManager.ts} +21 -15
- package/src/{packetTrailer/packetTrailer.test.ts → frameMetadata/frameMetadata.test.ts} +84 -1
- package/src/{packetTrailer/packetTrailer.ts → frameMetadata/frameMetadata.ts} +17 -9
- package/src/{packetTrailer → frameMetadata}/types.ts +13 -6
- package/src/{packetTrailer → frameMetadata}/utils.test.ts +19 -19
- package/src/{packetTrailer → frameMetadata}/utils.ts +11 -11
- package/src/{packetTrailer/worker/packetTrailer.worker.ts → frameMetadata/worker/frameMetadata.worker.ts} +5 -5
- package/src/{packetTrailer → frameMetadata}/worker/tsconfig.json +4 -1
- package/src/index.ts +13 -2
- package/src/options.ts +9 -4
- package/src/room/PCTransport.test.ts +150 -0
- package/src/room/PCTransport.ts +117 -0
- package/src/room/PCTransportManager.ts +4 -0
- package/src/room/RTCEngine.test.ts +143 -9
- package/src/room/RTCEngine.ts +112 -31
- package/src/room/Room.ts +11 -10
- package/src/room/data-track/depacketizer.ts +1 -0
- package/src/room/data-track/frame.ts +1 -0
- package/src/room/data-track/outgoing/types.ts +1 -0
- package/src/room/data-track/packet/extensions.ts +1 -0
- package/src/room/data-track/packet/index.ts +1 -0
- package/src/room/data-track/packet/serializable.ts +1 -0
- package/src/room/participant/LocalParticipant.test.ts +19 -19
- package/src/room/participant/LocalParticipant.ts +22 -16
- package/src/room/participant/Participant.ts +1 -0
- package/src/room/participant/publishUtils.test.ts +6 -0
- package/src/room/participant/publishUtils.ts +4 -1
- package/src/room/rpc/client/RpcClientManager.ts +1 -1
- package/src/room/rpc/server/RpcServerManager.ts +1 -1
- package/src/room/token-source/TokenSource.ts +9 -23
- package/src/room/token-source/types.ts +1 -0
- package/src/room/token-source/utils.ts +1 -0
- package/src/room/track/{PacketTrailerExtractor.ts → FrameMetadataExtractor.ts} +8 -8
- package/src/room/track/RemoteVideoTrack.ts +7 -11
- package/src/room/track/Track.ts +1 -0
- package/src/room/track/options.ts +19 -5
- package/src/room/track/record.ts +1 -0
- package/src/room/utils.ts +1 -0
- package/src/type-polyfills/{non-shared-typed-arrays.d.ts → non-shared-typed-arrays.ts} +6 -1
- package/src/utils/dataPacketBuffer.ts +2 -0
- package/dist/livekit-client.pt.worker.js +0 -2
- package/dist/livekit-client.pt.worker.mjs.map +0 -1
- package/dist/src/packetTrailer/packetTrailer.d.ts.map +0 -1
- package/dist/src/packetTrailer/types.d.ts.map +0 -1
- package/dist/src/packetTrailer/utils.d.ts +0 -9
- package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts +0 -2
- package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts.map +0 -1
- package/dist/src/room/track/PacketTrailerExtractor.d.ts +0 -19
- package/dist/src/room/track/PacketTrailerExtractor.d.ts.map +0 -1
- package/dist/ts4.2/packetTrailer/utils.d.ts +0 -9
- package/dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts +0 -2
- package/dist/ts4.2/room/track/PacketTrailerExtractor.d.ts +0 -19
package/src/room/RTCEngine.ts
CHANGED
|
@@ -54,13 +54,14 @@ import {
|
|
|
54
54
|
} from '../api/SignalClient';
|
|
55
55
|
import type { BaseE2EEManager } from '../e2ee/E2eeManager';
|
|
56
56
|
import { asEncryptablePacket, isInsertableStreamSupported } from '../e2ee/utils';
|
|
57
|
+
import {
|
|
58
|
+
hasFrameMetadataPublishOptions,
|
|
59
|
+
isFrameMetadataSupported,
|
|
60
|
+
shouldUseFrameMetadataScriptTransform,
|
|
61
|
+
} from '../frameMetadata/utils';
|
|
57
62
|
import log, { LoggerNames, getLogger } from '../logger';
|
|
58
63
|
import type { InternalRoomOptions } from '../options';
|
|
59
|
-
import {
|
|
60
|
-
hasPacketTrailerPublishOptions,
|
|
61
|
-
isPacketTrailerSupported,
|
|
62
|
-
shouldUsePacketTrailerScriptTransform,
|
|
63
|
-
} from '../packetTrailer/utils';
|
|
64
|
+
import type { NonSharedUint8Array } from '../type-polyfills/non-shared-typed-arrays';
|
|
64
65
|
import TypedPromise from '../utils/TypedPromise';
|
|
65
66
|
import { DataPacketBuffer } from '../utils/dataPacketBuffer';
|
|
66
67
|
import { TTLMap } from '../utils/ttlmap';
|
|
@@ -73,6 +74,7 @@ import {
|
|
|
73
74
|
ConnectionError,
|
|
74
75
|
ConnectionErrorReason,
|
|
75
76
|
NegotiationError,
|
|
77
|
+
PublishDataError,
|
|
76
78
|
SignalReconnectError,
|
|
77
79
|
TrackInvalidError,
|
|
78
80
|
UnexpectedConnectionState,
|
|
@@ -91,6 +93,7 @@ import type { LoggerOptions } from './types';
|
|
|
91
93
|
import {
|
|
92
94
|
Future,
|
|
93
95
|
isPublisherOfferWithJoinSupported,
|
|
96
|
+
isReactNative,
|
|
94
97
|
isVideoCodec,
|
|
95
98
|
isVideoTrack,
|
|
96
99
|
isWeb,
|
|
@@ -108,6 +111,7 @@ const leaveReconnect = 'leave-reconnect';
|
|
|
108
111
|
const reliabeReceiveStateTTL = 30_000;
|
|
109
112
|
const lossyDataChannelBufferThresholdMin = 8 * 1024;
|
|
110
113
|
const lossyDataChannelBufferThresholdMax = 256 * 1024;
|
|
114
|
+
|
|
111
115
|
const initialMediaSectionsAudio = 3;
|
|
112
116
|
const initialMediaSectionsVideo = 3;
|
|
113
117
|
|
|
@@ -125,6 +129,11 @@ export enum DataChannelKind {
|
|
|
125
129
|
DATA_TRACK_LOSSY = 2,
|
|
126
130
|
}
|
|
127
131
|
|
|
132
|
+
// Default data-channel max message size (bytes), used when the remote SDP
|
|
133
|
+
// answer does not advertise an `a=max-message-size` attribute (RFC 8841).
|
|
134
|
+
// `0` means "no limit".
|
|
135
|
+
const DEFAULT_MAX_MESSAGE_SIZE = 64_000;
|
|
136
|
+
|
|
128
137
|
/** @internal */
|
|
129
138
|
export default class RTCEngine extends (EventEmitter as new () => TypedEventEmitter<EngineEventCallbacks>) {
|
|
130
139
|
client: SignalClient;
|
|
@@ -449,18 +458,25 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
449
458
|
}
|
|
450
459
|
|
|
451
460
|
async cleanupPeerConnections() {
|
|
452
|
-
await this.pcManager?.close();
|
|
453
|
-
this.pcManager = undefined;
|
|
454
|
-
|
|
455
461
|
const dcCleanup = (dc: RTCDataChannel | undefined) => {
|
|
456
|
-
if (!dc)
|
|
457
|
-
|
|
462
|
+
if (!dc) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Detach the data channel handlers before closing anything. Closing a peer connection tears
|
|
467
|
+
// down the SCTP transport, which can dispatch `error`/`close` events on the still-open data
|
|
468
|
+
// channels; if our handlers are still attached at that point, handleDataError logs a spurious
|
|
469
|
+
// "Unknown DataChannel error" during an otherwise graceful disconnect. Removing the handlers
|
|
470
|
+
// before dc.close()/pcManager.close() makes this deterministic regardless of how/when the
|
|
471
|
+
// browser dispatches those teardown events. See livekit/client-sdk-js#1953.
|
|
458
472
|
dc.onbufferedamountlow = null;
|
|
459
473
|
dc.onclose = null;
|
|
460
474
|
dc.onclosing = null;
|
|
461
475
|
dc.onerror = null;
|
|
462
476
|
dc.onmessage = null;
|
|
463
477
|
dc.onopen = null;
|
|
478
|
+
|
|
479
|
+
dc.close();
|
|
464
480
|
};
|
|
465
481
|
dcCleanup(this.lossyDC);
|
|
466
482
|
dcCleanup(this.lossyDCSub);
|
|
@@ -469,6 +485,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
469
485
|
dcCleanup(this.dataTrackDC);
|
|
470
486
|
dcCleanup(this.dataTrackDCSub);
|
|
471
487
|
|
|
488
|
+
await this.pcManager?.close();
|
|
489
|
+
this.pcManager = undefined;
|
|
490
|
+
|
|
472
491
|
this.lossyDC = undefined;
|
|
473
492
|
this.lossyDCSub = undefined;
|
|
474
493
|
this.reliableDC = undefined;
|
|
@@ -788,7 +807,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
788
807
|
// used; RTCRtpScriptTransform does not need the PeerConnection flag.
|
|
789
808
|
const needsInsertableStreams =
|
|
790
809
|
this.signalOpts?.e2eeEnabled ||
|
|
791
|
-
(this.
|
|
810
|
+
(this.frameMetadataWorker && !shouldUseFrameMetadataScriptTransform());
|
|
792
811
|
|
|
793
812
|
if (needsInsertableStreams && isInsertableStreamSupported()) {
|
|
794
813
|
this.log.debug('E2EE - setting up transports with insertable streams');
|
|
@@ -840,7 +859,15 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
840
859
|
*/
|
|
841
860
|
private applyInitialPublisherLayout() {
|
|
842
861
|
this.createDataChannels();
|
|
843
|
-
|
|
862
|
+
/**
|
|
863
|
+
* Native libwebrtc does not support pre-populating the media sections,
|
|
864
|
+
* so we skip it for React Native.
|
|
865
|
+
*
|
|
866
|
+
* Related: https://github.com/livekit/rust-sdks/pull/1151
|
|
867
|
+
*/
|
|
868
|
+
if (!isReactNative()) {
|
|
869
|
+
this.addMediaSections(initialMediaSectionsAudio, initialMediaSectionsVideo);
|
|
870
|
+
}
|
|
844
871
|
}
|
|
845
872
|
|
|
846
873
|
private addMediaSections(numAudios: number, numVideos: number) {
|
|
@@ -862,14 +889,17 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
862
889
|
if (this.lossyDC) {
|
|
863
890
|
this.lossyDC.onmessage = null;
|
|
864
891
|
this.lossyDC.onerror = null;
|
|
892
|
+
this.lossyDC.onclose = null;
|
|
865
893
|
}
|
|
866
894
|
if (this.reliableDC) {
|
|
867
895
|
this.reliableDC.onmessage = null;
|
|
868
896
|
this.reliableDC.onerror = null;
|
|
897
|
+
this.reliableDC.onclose = null;
|
|
869
898
|
}
|
|
870
899
|
if (this.dataTrackDC) {
|
|
871
900
|
this.dataTrackDC.onmessage = null;
|
|
872
901
|
this.dataTrackDC.onerror = null;
|
|
902
|
+
this.dataTrackDC.onclose = null;
|
|
873
903
|
}
|
|
874
904
|
|
|
875
905
|
// create data channels
|
|
@@ -895,6 +925,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
895
925
|
this.reliableDC.onerror = this.handleDataError;
|
|
896
926
|
this.dataTrackDC.onerror = this.handleDataError;
|
|
897
927
|
|
|
928
|
+
// detect unexpected publisher data channel closes
|
|
929
|
+
this.lossyDC.onclose = this.handleDataChannelClose(DataChannelKind.LOSSY);
|
|
930
|
+
this.reliableDC.onclose = this.handleDataChannelClose(DataChannelKind.RELIABLE);
|
|
931
|
+
this.dataTrackDC.onclose = this.handleDataChannelClose(DataChannelKind.DATA_TRACK_LOSSY);
|
|
932
|
+
|
|
898
933
|
// set up dc buffer threshold, set to 64kB (otherwise 0 by default)
|
|
899
934
|
this.lossyDC.bufferedAmountLowThreshold = 65535;
|
|
900
935
|
this.reliableDC.bufferedAmountLowThreshold = 65535;
|
|
@@ -1023,17 +1058,41 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1023
1058
|
};
|
|
1024
1059
|
|
|
1025
1060
|
private handleDataError = (event: Event) => {
|
|
1061
|
+
// Errors fired while we're tearing the connection down (e.g. the SCTP transport aborting as
|
|
1062
|
+
// the peer connection closes) carry no actionable information — the channel is going away
|
|
1063
|
+
// regardless. Suppress them so a graceful disconnect doesn't surface spurious errors.
|
|
1064
|
+
// See livekit/client-sdk-js#1953.
|
|
1065
|
+
if (this._isClosed) {
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1026
1069
|
const channel = event.currentTarget as RTCDataChannel;
|
|
1027
1070
|
const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
|
|
1028
1071
|
|
|
1029
|
-
if (event instanceof
|
|
1030
|
-
const { error } = event
|
|
1031
|
-
this.log.error(`DataChannel error on ${channelKind}: ${
|
|
1072
|
+
if (typeof RTCErrorEvent !== 'undefined' && event instanceof RTCErrorEvent && event.error) {
|
|
1073
|
+
const { error } = event;
|
|
1074
|
+
this.log.error(`DataChannel error on ${channelKind}: ${error.message}`, {
|
|
1075
|
+
error,
|
|
1076
|
+
errorDetail: error.errorDetail,
|
|
1077
|
+
sctpCauseCode: error.sctpCauseCode,
|
|
1078
|
+
});
|
|
1032
1079
|
} else {
|
|
1033
1080
|
this.log.error(`Unknown DataChannel error on ${channelKind}`, { event });
|
|
1034
1081
|
}
|
|
1035
1082
|
};
|
|
1036
1083
|
|
|
1084
|
+
private handleDataChannelClose = (kind: DataChannelKind) => () => {
|
|
1085
|
+
// A publisher DC closing while the session is up and the publisher PC is still
|
|
1086
|
+
// connected is the signature of an oversized message having aborted the channel
|
|
1087
|
+
// (see livekit/rust-sdks#1137). Surface it; do not attempt renegotiation.
|
|
1088
|
+
if (!this._isClosed && this.pcManager?.publisher.getConnectionState() === 'connected') {
|
|
1089
|
+
this.log.error(
|
|
1090
|
+
`publisher data channel '${DataChannelKind[kind]}' closed unexpectedly`,
|
|
1091
|
+
this.logContext,
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1037
1096
|
private handleBufferedAmountLow = (channelKind: DataChannelKind) => {
|
|
1038
1097
|
this.updateAndEmitDCBufferStatus(channelKind);
|
|
1039
1098
|
};
|
|
@@ -1052,7 +1111,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1052
1111
|
} else {
|
|
1053
1112
|
throw new UnexpectedConnectionState('Required webRTC APIs not supported on this device');
|
|
1054
1113
|
}
|
|
1055
|
-
this.
|
|
1114
|
+
this.setupFrameMetadataSender(sender, opts);
|
|
1056
1115
|
return sender;
|
|
1057
1116
|
}
|
|
1058
1117
|
|
|
@@ -1072,50 +1131,55 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1072
1131
|
throw new UnexpectedConnectionState('Cannot stream on this device');
|
|
1073
1132
|
}
|
|
1074
1133
|
if (sender) {
|
|
1075
|
-
this.
|
|
1134
|
+
this.setupFrameMetadataSender(sender, opts);
|
|
1076
1135
|
}
|
|
1077
1136
|
return sender;
|
|
1078
1137
|
}
|
|
1079
1138
|
|
|
1080
|
-
private
|
|
1081
|
-
|
|
1139
|
+
private get frameMetadataWorker(): Worker | undefined {
|
|
1140
|
+
return (this.options.frameMetadata ?? this.options.packetTrailer)?.worker;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
private setupFrameMetadataSender(sender: RTCRtpSender, opts: TrackPublishOptions = {}) {
|
|
1144
|
+
const worker = this.frameMetadataWorker;
|
|
1145
|
+
if (!worker || this.signalOpts?.e2eeEnabled) {
|
|
1082
1146
|
return;
|
|
1083
1147
|
}
|
|
1084
1148
|
|
|
1085
|
-
const
|
|
1086
|
-
const
|
|
1149
|
+
const frameMetadata = opts.frameMetadata ?? opts.packetTrailer;
|
|
1150
|
+
const hasMetadata = hasFrameMetadataPublishOptions(frameMetadata);
|
|
1087
1151
|
|
|
1088
|
-
if (
|
|
1089
|
-
if (
|
|
1152
|
+
if (shouldUseFrameMetadataScriptTransform()) {
|
|
1153
|
+
if (hasMetadata) {
|
|
1090
1154
|
// @ts-ignore
|
|
1091
|
-
sender.transform = new RTCRtpScriptTransform(
|
|
1155
|
+
sender.transform = new RTCRtpScriptTransform(worker, {
|
|
1092
1156
|
kind: 'encode',
|
|
1093
|
-
packetTrailer,
|
|
1157
|
+
packetTrailer: frameMetadata,
|
|
1094
1158
|
});
|
|
1095
1159
|
}
|
|
1096
1160
|
return;
|
|
1097
1161
|
}
|
|
1098
1162
|
|
|
1099
1163
|
if (
|
|
1100
|
-
!
|
|
1164
|
+
!isFrameMetadataSupported(this.options.frameMetadata ?? this.options.packetTrailer) ||
|
|
1101
1165
|
!('createEncodedStreams' in sender)
|
|
1102
1166
|
) {
|
|
1103
|
-
if (
|
|
1104
|
-
this.log.warn('
|
|
1167
|
+
if (hasMetadata) {
|
|
1168
|
+
this.log.warn('frame metadata transform not supported; skipping write', this.logContext);
|
|
1105
1169
|
}
|
|
1106
1170
|
return;
|
|
1107
1171
|
}
|
|
1108
1172
|
|
|
1109
1173
|
// @ts-ignore
|
|
1110
1174
|
const { readable, writable } = sender.createEncodedStreams();
|
|
1111
|
-
if (
|
|
1112
|
-
|
|
1175
|
+
if (hasMetadata) {
|
|
1176
|
+
worker.postMessage(
|
|
1113
1177
|
{
|
|
1114
1178
|
kind: 'encode',
|
|
1115
1179
|
data: {
|
|
1116
1180
|
readableStream: readable,
|
|
1117
1181
|
writableStream: writable,
|
|
1118
|
-
packetTrailer,
|
|
1182
|
+
packetTrailer: frameMetadata,
|
|
1119
1183
|
},
|
|
1120
1184
|
},
|
|
1121
1185
|
[readable, writable],
|
|
@@ -1535,6 +1599,23 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1535
1599
|
|
|
1536
1600
|
const msg = packet.toBinary() as Uint8Array<ArrayBuffer>;
|
|
1537
1601
|
|
|
1602
|
+
// Clamp to the SDK default - libwebrtc advertises larger (~256 KiB)
|
|
1603
|
+
// than LiveKit/pion can deliver end-to-end (~64 KiB), so we trust
|
|
1604
|
+
// the answer up untilthe built in ceiling.
|
|
1605
|
+
const maxPublisherMessageSizeBytes = Math.min(
|
|
1606
|
+
this.pcManager?.getMaxPublisherMessageSize() ?? DEFAULT_MAX_MESSAGE_SIZE,
|
|
1607
|
+
DEFAULT_MAX_MESSAGE_SIZE,
|
|
1608
|
+
);
|
|
1609
|
+
if (
|
|
1610
|
+
typeof maxPublisherMessageSizeBytes !== 'undefined' &&
|
|
1611
|
+
maxPublisherMessageSizeBytes !== 0 /* 0 means "no limit" */ &&
|
|
1612
|
+
msg.byteLength > maxPublisherMessageSizeBytes
|
|
1613
|
+
) {
|
|
1614
|
+
throw new PublishDataError(
|
|
1615
|
+
`cannot publish data packet larger than ${maxPublisherMessageSizeBytes} bytes (got ${msg.byteLength})`,
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1538
1619
|
switch (kind) {
|
|
1539
1620
|
case DataChannelKind.LOSSY:
|
|
1540
1621
|
case DataChannelKind.DATA_TRACK_LOSSY:
|
package/src/room/Room.ts
CHANGED
|
@@ -37,6 +37,8 @@ import type TypedEmitter from 'typed-emitter';
|
|
|
37
37
|
import { ensureTrailingSlash } from '../api/utils';
|
|
38
38
|
import { EncryptionEvent } from '../e2ee';
|
|
39
39
|
import { type BaseE2EEManager, E2EEManager } from '../e2ee/E2eeManager';
|
|
40
|
+
import { FrameMetadataManager } from '../frameMetadata/FrameMetadataManager';
|
|
41
|
+
import { isFrameMetadataSupported } from '../frameMetadata/utils';
|
|
40
42
|
import log, { LoggerNames, getLogger } from '../logger';
|
|
41
43
|
import type {
|
|
42
44
|
InternalRoomConnectOptions,
|
|
@@ -44,8 +46,7 @@ import type {
|
|
|
44
46
|
RoomConnectOptions,
|
|
45
47
|
RoomOptions,
|
|
46
48
|
} from '../options';
|
|
47
|
-
import {
|
|
48
|
-
import { isPacketTrailerSupported } from '../packetTrailer/utils';
|
|
49
|
+
import type { NonSharedUint8Array } from '../type-polyfills/non-shared-typed-arrays';
|
|
49
50
|
import TypedPromise from '../utils/TypedPromise';
|
|
50
51
|
import { getBrowser } from '../utils/browserParser';
|
|
51
52
|
import { CLIENT_PROTOCOL_DEFAULT } from '../version';
|
|
@@ -198,7 +199,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
198
199
|
|
|
199
200
|
private e2eeManager: BaseE2EEManager | undefined;
|
|
200
201
|
|
|
201
|
-
private
|
|
202
|
+
private frameMetadataManager: FrameMetadataManager | undefined;
|
|
202
203
|
|
|
203
204
|
private e2eeStateMutex: Mutex = new Mutex();
|
|
204
205
|
|
|
@@ -343,7 +344,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
343
344
|
this.rpcServerManager,
|
|
344
345
|
);
|
|
345
346
|
|
|
346
|
-
this.
|
|
347
|
+
this.setupFrameMetadata();
|
|
347
348
|
|
|
348
349
|
if (this.options.e2ee || this.options.encryption) {
|
|
349
350
|
this.setupE2EE();
|
|
@@ -514,11 +515,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
514
515
|
}
|
|
515
516
|
}
|
|
516
517
|
|
|
517
|
-
private
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
this.
|
|
521
|
-
this.packetTrailerManager.setup(this);
|
|
518
|
+
private setupFrameMetadata() {
|
|
519
|
+
const opts = this.options.frameMetadata ?? this.options.packetTrailer;
|
|
520
|
+
this.frameMetadataManager = new FrameMetadataManager(opts);
|
|
521
|
+
this.frameMetadataManager.setup(this);
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
private get logContext() {
|
|
@@ -972,7 +972,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
972
972
|
adaptiveStream:
|
|
973
973
|
typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
|
|
974
974
|
clientInfoCapabilities:
|
|
975
|
-
|
|
975
|
+
isFrameMetadataSupported(roomOptions.frameMetadata ?? roomOptions.packetTrailer) ||
|
|
976
|
+
!!this.e2eeManager
|
|
976
977
|
? [ClientInfo_Capability.CAP_PACKET_TRAILER]
|
|
977
978
|
: undefined,
|
|
978
979
|
maxRetries: connectOptions.maxRetries,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Throws } from '@livekit/throws-transformer/throws';
|
|
2
2
|
import { LoggerNames, getLogger } from '../../logger';
|
|
3
|
+
import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
|
|
3
4
|
import { LivekitReasonedError } from '../errors';
|
|
4
5
|
import { type DataTrackFrameInternal } from './frame';
|
|
5
6
|
import { DataTrackPacket, FrameMarker } from './packet';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
|
|
1
2
|
import { DataTrackExtensions, DataTrackUserTimestampExtension } from './packet/extensions';
|
|
2
3
|
|
|
3
4
|
/** A pair of payload bytes and packet extensions which can be fed into a {@link DataTrackPacketizer}. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Throws } from '@livekit/throws-transformer/throws';
|
|
2
|
+
import type { NonSharedUint8Array } from '../../../type-polyfills/non-shared-typed-arrays';
|
|
2
3
|
import { coerceToDataView } from '../utils';
|
|
3
4
|
import { EXT_TAG_PADDING, U8_LENGTH_BYTES, U64_LENGTH_BYTES } from './constants';
|
|
4
5
|
import { DataTrackDeserializeError, DataTrackDeserializeErrorReason } from './errors';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Throws } from '@livekit/throws-transformer/throws';
|
|
2
|
+
import type { NonSharedUint8Array } from '../../../type-polyfills/non-shared-typed-arrays';
|
|
2
3
|
import { DataTrackHandle, DataTrackHandleError, DataTrackHandleErrorReason } from '../handle';
|
|
3
4
|
import {
|
|
4
5
|
DataTrackTimestamp,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Throws } from '@livekit/throws-transformer/throws';
|
|
2
|
+
import type { NonSharedUint8Array } from '../../../type-polyfills/non-shared-typed-arrays';
|
|
2
3
|
import { DataTrackSerializeError } from './errors';
|
|
3
4
|
|
|
4
5
|
/** An abstract class implementing common behavior related to data track binary serialization. */
|
|
@@ -5,18 +5,18 @@ import { Track } from '../track/Track';
|
|
|
5
5
|
import type { TrackPublishOptions } from '../track/options';
|
|
6
6
|
import LocalParticipant from './LocalParticipant';
|
|
7
7
|
|
|
8
|
-
type
|
|
9
|
-
|
|
8
|
+
type FrameMetadataTestParticipant = {
|
|
9
|
+
canPublishFrameMetadata: () => boolean;
|
|
10
10
|
log: { warn: ReturnType<typeof vi.fn> };
|
|
11
|
-
|
|
11
|
+
normalizeRequestedFrameMetadataOptions: (
|
|
12
12
|
track: LocalTrack,
|
|
13
13
|
opts: TrackPublishOptions,
|
|
14
14
|
) => PacketTrailerFeature[];
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
function makeParticipant(
|
|
18
|
-
const participant = Object.create(LocalParticipant.prototype) as
|
|
19
|
-
participant.
|
|
17
|
+
function makeParticipant(canPublishFrameMetadata: boolean) {
|
|
18
|
+
const participant = Object.create(LocalParticipant.prototype) as FrameMetadataTestParticipant;
|
|
19
|
+
participant.canPublishFrameMetadata = () => canPublishFrameMetadata;
|
|
20
20
|
participant.log = { warn: vi.fn() };
|
|
21
21
|
return participant;
|
|
22
22
|
}
|
|
@@ -35,12 +35,12 @@ function makeTrack(kind: Track.Kind) {
|
|
|
35
35
|
} as unknown as LocalTrack;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
describe('LocalParticipant
|
|
39
|
-
it('normalizes requested video
|
|
38
|
+
describe('LocalParticipant frame metadata publish options', () => {
|
|
39
|
+
it('normalizes requested video frame metadata options to advertised features', () => {
|
|
40
40
|
const participant = makeParticipant(true);
|
|
41
|
-
const opts: TrackPublishOptions = {
|
|
41
|
+
const opts: TrackPublishOptions = { frameMetadata: { timestamp: true, frameId: true } };
|
|
42
42
|
|
|
43
|
-
const features = participant.
|
|
43
|
+
const features = participant.normalizeRequestedFrameMetadataOptions(
|
|
44
44
|
makeTrack(Track.Kind.Video),
|
|
45
45
|
opts,
|
|
46
46
|
);
|
|
@@ -49,33 +49,33 @@ describe('LocalParticipant packet trailer publish options', () => {
|
|
|
49
49
|
PacketTrailerFeature.PTF_USER_TIMESTAMP,
|
|
50
50
|
PacketTrailerFeature.PTF_FRAME_ID,
|
|
51
51
|
]);
|
|
52
|
-
expect(opts.
|
|
52
|
+
expect(opts.frameMetadata).toEqual({ timestamp: true, frameId: true });
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
it('clears
|
|
55
|
+
it('clears frame metadata options for non-video tracks', () => {
|
|
56
56
|
const participant = makeParticipant(true);
|
|
57
|
-
const opts: TrackPublishOptions = {
|
|
57
|
+
const opts: TrackPublishOptions = { frameMetadata: { timestamp: true } };
|
|
58
58
|
|
|
59
|
-
const features = participant.
|
|
59
|
+
const features = participant.normalizeRequestedFrameMetadataOptions(
|
|
60
60
|
makeTrack(Track.Kind.Audio),
|
|
61
61
|
opts,
|
|
62
62
|
);
|
|
63
63
|
|
|
64
64
|
expect(features).toEqual([]);
|
|
65
|
-
expect(opts.
|
|
65
|
+
expect(opts.frameMetadata).toBeUndefined();
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
it('clears
|
|
68
|
+
it('clears frame metadata options when publishing frame metadata is unsupported', () => {
|
|
69
69
|
const participant = makeParticipant(false);
|
|
70
|
-
const opts: TrackPublishOptions = {
|
|
70
|
+
const opts: TrackPublishOptions = { frameMetadata: { frameId: true } };
|
|
71
71
|
|
|
72
|
-
const features = participant.
|
|
72
|
+
const features = participant.normalizeRequestedFrameMetadataOptions(
|
|
73
73
|
makeTrack(Track.Kind.Video),
|
|
74
74
|
opts,
|
|
75
75
|
);
|
|
76
76
|
|
|
77
77
|
expect(features).toEqual([]);
|
|
78
|
-
expect(opts.
|
|
78
|
+
expect(opts.frameMetadata).toBeUndefined();
|
|
79
79
|
expect(participant.log.warn).toHaveBeenCalledOnce();
|
|
80
80
|
});
|
|
81
81
|
});
|
|
@@ -22,13 +22,14 @@ import {
|
|
|
22
22
|
protoInt64,
|
|
23
23
|
} from '@livekit/protocol';
|
|
24
24
|
import { SignalConnectionState } from '../../api/SignalClient';
|
|
25
|
-
import type { InternalRoomOptions } from '../../options';
|
|
26
25
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} from '../../
|
|
26
|
+
getFrameMetadataFeatures,
|
|
27
|
+
getFrameMetadataPublishOptions,
|
|
28
|
+
hasFrameMetadataPublishOptions,
|
|
29
|
+
isFrameMetadataSupported,
|
|
30
|
+
} from '../../frameMetadata/utils';
|
|
31
|
+
import type { InternalRoomOptions } from '../../options';
|
|
32
|
+
import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
|
|
32
33
|
import TypedPromise from '../../utils/TypedPromise';
|
|
33
34
|
import { PCTransportState } from '../PCTransportManager';
|
|
34
35
|
import type RTCEngine from '../RTCEngine';
|
|
@@ -1085,7 +1086,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1085
1086
|
audioFeatures.push(AudioTrackFeature.TF_PRECONNECT_BUFFER);
|
|
1086
1087
|
}
|
|
1087
1088
|
const packetTrailerFeatures: PacketTrailerFeature[] =
|
|
1088
|
-
this.
|
|
1089
|
+
this.normalizeRequestedFrameMetadataOptions(track, opts);
|
|
1089
1090
|
|
|
1090
1091
|
// create track publication from track
|
|
1091
1092
|
const req = new AddTrackRequest({
|
|
@@ -1416,31 +1417,36 @@ export default class LocalParticipant extends Participant {
|
|
|
1416
1417
|
return publication;
|
|
1417
1418
|
}
|
|
1418
1419
|
|
|
1419
|
-
private
|
|
1420
|
+
private canPublishFrameMetadata() {
|
|
1420
1421
|
return !!(
|
|
1421
1422
|
this.roomOptions.e2ee ||
|
|
1422
1423
|
this.roomOptions.encryption ||
|
|
1423
|
-
|
|
1424
|
+
isFrameMetadataSupported(this.roomOptions.frameMetadata ?? this.roomOptions.packetTrailer)
|
|
1424
1425
|
);
|
|
1425
1426
|
}
|
|
1426
1427
|
|
|
1427
|
-
private
|
|
1428
|
-
|
|
1428
|
+
private normalizeRequestedFrameMetadataOptions(track: LocalTrack, opts: TrackPublishOptions) {
|
|
1429
|
+
const fmOpts = opts.frameMetadata ?? opts.packetTrailer;
|
|
1430
|
+
if (track.kind !== Track.Kind.Video || !hasFrameMetadataPublishOptions(fmOpts)) {
|
|
1431
|
+
opts.frameMetadata = undefined;
|
|
1429
1432
|
opts.packetTrailer = undefined;
|
|
1430
1433
|
return [];
|
|
1431
1434
|
}
|
|
1432
1435
|
|
|
1433
|
-
if (!this.
|
|
1434
|
-
this.log.warn('
|
|
1436
|
+
if (!this.canPublishFrameMetadata()) {
|
|
1437
|
+
this.log.warn('frame metadata transform not supported; not advertising features', {
|
|
1435
1438
|
...this.logContext,
|
|
1436
1439
|
...getLogContextFromTrack(track),
|
|
1437
1440
|
});
|
|
1441
|
+
opts.frameMetadata = undefined;
|
|
1438
1442
|
opts.packetTrailer = undefined;
|
|
1439
1443
|
return [];
|
|
1440
1444
|
}
|
|
1441
1445
|
|
|
1442
|
-
const features =
|
|
1443
|
-
|
|
1446
|
+
const features = getFrameMetadataFeatures(fmOpts);
|
|
1447
|
+
const normalized = getFrameMetadataPublishOptions(features);
|
|
1448
|
+
opts.frameMetadata = normalized;
|
|
1449
|
+
opts.packetTrailer = normalized;
|
|
1444
1450
|
return features;
|
|
1445
1451
|
}
|
|
1446
1452
|
|
|
@@ -1496,7 +1502,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1496
1502
|
if (!simulcastTrack) {
|
|
1497
1503
|
return;
|
|
1498
1504
|
}
|
|
1499
|
-
const packetTrailerFeatures = this.
|
|
1505
|
+
const packetTrailerFeatures = this.normalizeRequestedFrameMetadataOptions(track, opts);
|
|
1500
1506
|
|
|
1501
1507
|
const req = new AddTrackRequest({
|
|
1502
1508
|
cid: simulcastTrack.mediaStreamTrack.id,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { EventEmitter } from 'events';
|
|
13
13
|
import type TypedEmitter from 'typed-emitter';
|
|
14
14
|
import log, { LoggerNames, type StructuredLogger, getLogger } from '../../logger';
|
|
15
|
+
import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
|
|
15
16
|
import { ParticipantEvent, TrackEvent } from '../events';
|
|
16
17
|
import type LocalTrackPublication from '../track/LocalTrackPublication';
|
|
17
18
|
import type LocalVideoTrack from '../track/LocalVideoTrack';
|
|
@@ -174,6 +174,12 @@ describe('customSimulcastLayers', () => {
|
|
|
174
174
|
expect(sortedPresets[1].encoding.maxFramerate).toBe(15);
|
|
175
175
|
expect(sortedPresets[2].encoding.maxFramerate).toBe(20);
|
|
176
176
|
});
|
|
177
|
+
it('does not mutate the passed-in array', () => {
|
|
178
|
+
const presets = [VideoPresets.h1440, VideoPresets.h360, VideoPresets.h1080, VideoPresets.h90];
|
|
179
|
+
const original = [...presets];
|
|
180
|
+
sortPresets(presets);
|
|
181
|
+
expect(presets).toEqual(original);
|
|
182
|
+
});
|
|
177
183
|
});
|
|
178
184
|
|
|
179
185
|
describe('screenShareSimulcastDefaults', () => {
|
|
@@ -419,7 +419,10 @@ function encodingsFromPresets(
|
|
|
419
419
|
/** @internal */
|
|
420
420
|
export function sortPresets(presets: Array<VideoPreset> | undefined) {
|
|
421
421
|
if (!presets) return;
|
|
422
|
-
|
|
422
|
+
// Sort a copy so we don't mutate the caller's array in place. Mutating the
|
|
423
|
+
// passed-in simulcast layers can cause consumers that compare options by value
|
|
424
|
+
// (e.g. components-react's useLiveKitRoom) to detect a spurious change.
|
|
425
|
+
return presets.slice().sort((a, b) => {
|
|
423
426
|
const { encoding: aEnc } = a;
|
|
424
427
|
const { encoding: bEnc } = b;
|
|
425
428
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DataPacket, DataPacket_Kind, RpcRequest } from '@livekit/protocol';
|
|
2
|
-
import EventEmitter from 'events';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
3
|
import type TypedEmitter from 'typed-emitter';
|
|
4
4
|
import { type StructuredLogger } from '../../../logger';
|
|
5
5
|
import { CLIENT_PROTOCOL_DATA_STREAM_RPC } from '../../../version';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DataPacket, DataPacket_Kind, RpcAck, RpcRequest, RpcResponse } from '@livekit/protocol';
|
|
2
|
-
import EventEmitter from 'events';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
3
|
import type TypedEmitter from 'typed-emitter';
|
|
4
4
|
import { type StructuredLogger } from '../../../logger';
|
|
5
5
|
import { CLIENT_PROTOCOL_DATA_STREAM_RPC } from '../../../version';
|