livekit-client 2.18.9 → 2.18.10
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 +5609 -644
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +2870 -2420
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.pt.worker.js +2 -0
- package/dist/livekit-client.pt.worker.js.map +1 -0
- package/dist/livekit-client.pt.worker.mjs +5834 -0
- package/dist/livekit-client.pt.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 +2 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +8 -7
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +35 -8
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +5 -5
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts +5 -5
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +21 -4
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/sifPayload.d.ts +7 -7
- package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/options.d.ts +7 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/packetTrailer/PacketTrailerManager.d.ts +49 -0
- package/dist/src/packetTrailer/PacketTrailerManager.d.ts.map +1 -0
- package/dist/src/packetTrailer/packetTrailer.d.ts +32 -0
- package/dist/src/packetTrailer/packetTrailer.d.ts.map +1 -0
- package/dist/src/packetTrailer/types.d.ts +57 -0
- package/dist/src/packetTrailer/types.d.ts.map +1 -0
- package/dist/src/packetTrailer/utils.d.ts +9 -0
- package/dist/src/packetTrailer/utils.d.ts.map +1 -0
- package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
- package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts.map +1 -0
- package/dist/src/room/RTCEngine.d.ts +2 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +3 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-track/RemoteDataTrack.d.ts +5 -1
- package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
- package/dist/src/room/data-track/depacketizer.d.ts +12 -4
- package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
- package/dist/src/room/data-track/frame.d.ts +3 -3
- package/dist/src/room/data-track/frame.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/pipeline.d.ts +4 -1
- package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/types.d.ts +2 -2
- package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/extensions.d.ts +4 -4
- package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/index.d.ts +5 -5
- package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/serializable.d.ts +1 -1
- package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
- package/dist/src/room/data-track/types.d.ts +7 -0
- package/dist/src/room/data-track/types.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +2 -2
- package/dist/src/room/participant/LocalParticipant.d.ts +3 -1
- 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/track/PacketTrailerExtractor.d.ts +19 -0
- package/dist/src/room/track/PacketTrailerExtractor.d.ts.map +1 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts +16 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +1 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/create.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +10 -0
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +4 -3
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -1
- package/dist/src/utils/dataPacketBuffer.d.ts +1 -1
- package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/ts4.2/api/SignalClient.d.ts +2 -1
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +8 -7
- package/dist/ts4.2/e2ee/types.d.ts +35 -8
- package/dist/ts4.2/e2ee/utils.d.ts +5 -5
- package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +5 -5
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +21 -4
- package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +1 -1
- package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +7 -7
- package/dist/ts4.2/index.d.ts +5 -1
- package/dist/ts4.2/options.d.ts +7 -0
- package/dist/ts4.2/packetTrailer/PacketTrailerManager.d.ts +49 -0
- package/dist/ts4.2/packetTrailer/packetTrailer.d.ts +32 -0
- package/dist/ts4.2/packetTrailer/types.d.ts +57 -0
- package/dist/ts4.2/packetTrailer/utils.d.ts +9 -0
- package/dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +2 -1
- package/dist/ts4.2/room/Room.d.ts +3 -1
- package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +5 -1
- package/dist/ts4.2/room/data-track/depacketizer.d.ts +12 -4
- package/dist/ts4.2/room/data-track/frame.d.ts +3 -3
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
- package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +4 -1
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +2 -2
- package/dist/ts4.2/room/data-track/packet/extensions.d.ts +4 -4
- package/dist/ts4.2/room/data-track/packet/index.d.ts +5 -5
- package/dist/ts4.2/room/data-track/packet/serializable.d.ts +1 -1
- package/dist/ts4.2/room/data-track/types.d.ts +7 -0
- package/dist/ts4.2/room/events.d.ts +2 -2
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +3 -1
- package/dist/ts4.2/room/participant/Participant.d.ts +1 -1
- package/dist/ts4.2/room/track/PacketTrailerExtractor.d.ts +19 -0
- package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +16 -0
- package/dist/ts4.2/room/track/Track.d.ts +1 -1
- package/dist/ts4.2/room/track/options.d.ts +10 -0
- package/dist/ts4.2/room/utils.d.ts +4 -3
- package/dist/ts4.2/utils/dataPacketBuffer.d.ts +1 -1
- package/dist/ts4.2/version.d.ts +1 -1
- package/package.json +24 -16
- package/src/api/SignalClient.test.ts +102 -10
- package/src/api/SignalClient.ts +4 -2
- package/src/api/WebSocketStream.test.ts +0 -1
- package/src/e2ee/E2eeManager.ts +82 -30
- package/src/e2ee/types.ts +37 -8
- package/src/e2ee/utils.ts +7 -6
- package/src/e2ee/worker/DataCryptor.ts +6 -6
- package/src/e2ee/worker/FrameCryptor.test.ts +177 -4
- package/src/e2ee/worker/FrameCryptor.ts +94 -14
- package/src/e2ee/worker/ParticipantKeyHandler.test.ts +4 -4
- package/src/e2ee/worker/e2ee.worker.ts +13 -5
- package/src/e2ee/worker/naluUtils.ts +4 -4
- package/src/e2ee/worker/sifPayload.ts +10 -8
- package/src/index.ts +7 -0
- package/src/options.ts +8 -0
- package/src/packetTrailer/PacketTrailerManager.test.ts +172 -0
- package/src/packetTrailer/PacketTrailerManager.ts +250 -0
- package/src/packetTrailer/packetTrailer.test.ts +174 -0
- package/src/packetTrailer/packetTrailer.ts +276 -0
- package/src/packetTrailer/types.ts +75 -0
- package/src/packetTrailer/utils.test.ts +105 -0
- package/src/packetTrailer/utils.ts +50 -0
- package/src/packetTrailer/worker/packetTrailer.worker.ts +155 -0
- package/src/packetTrailer/worker/tsconfig.json +14 -0
- package/src/room/BackOffStrategy.test.ts +1 -1
- package/src/room/RTCEngine.test.ts +219 -0
- package/src/room/RTCEngine.ts +86 -20
- package/src/room/Room.test.ts +62 -1
- package/src/room/Room.ts +28 -5
- package/src/room/data-track/RemoteDataTrack.ts +8 -1
- package/src/room/data-track/depacketizer.test.ts +433 -1
- package/src/room/data-track/depacketizer.ts +79 -61
- package/src/room/data-track/frame.ts +2 -2
- package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +194 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.ts +21 -1
- package/src/room/data-track/incoming/pipeline.ts +13 -2
- package/src/room/data-track/outgoing/types.ts +3 -2
- package/src/room/data-track/packet/extensions.ts +2 -2
- package/src/room/data-track/packet/index.ts +6 -6
- package/src/room/data-track/packet/serializable.ts +1 -1
- package/src/room/data-track/types.ts +8 -0
- package/src/room/events.ts +2 -2
- package/src/room/participant/LocalParticipant.test.ts +81 -0
- package/src/room/participant/LocalParticipant.ts +48 -7
- package/src/room/participant/Participant.ts +1 -1
- package/src/room/participant/publishUtils.ts +1 -1
- package/src/room/track/PacketTrailerExtractor.ts +43 -0
- package/src/room/track/RemoteVideoTrack.ts +23 -2
- package/src/room/track/Track.ts +1 -1
- package/src/room/track/create.ts +0 -4
- package/src/room/track/options.ts +11 -0
- package/src/room/track/record.ts +1 -1
- package/src/room/track/utils.ts +4 -1
- package/src/room/utils.test.ts +14 -1
- package/src/room/utils.ts +17 -3
- package/src/test/MockMediaStreamTrack.ts +0 -1
- package/src/type-polyfills/non-shared-typed-arrays.d.ts +6 -0
- package/src/utils/dataPacketBuffer.ts +1 -1
- package/src/version.ts +1 -1
package/src/room/Room.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Mutex } from '@livekit/mutex';
|
|
2
2
|
import {
|
|
3
3
|
ChatMessage as ChatMessageModel,
|
|
4
|
+
ClientInfo_Capability,
|
|
4
5
|
ConnectionQualityUpdate,
|
|
5
6
|
type DataPacket,
|
|
6
7
|
DataPacket_Kind,
|
|
@@ -43,6 +44,8 @@ import type {
|
|
|
43
44
|
RoomConnectOptions,
|
|
44
45
|
RoomOptions,
|
|
45
46
|
} from '../options';
|
|
47
|
+
import { PacketTrailerManager } from '../packetTrailer/PacketTrailerManager';
|
|
48
|
+
import { isPacketTrailerSupported } from '../packetTrailer/utils';
|
|
46
49
|
import TypedPromise from '../utils/TypedPromise';
|
|
47
50
|
import { getBrowser } from '../utils/browserParser';
|
|
48
51
|
import { BackOffStrategy } from './BackOffStrategy';
|
|
@@ -187,6 +190,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
187
190
|
|
|
188
191
|
private e2eeManager: BaseE2EEManager | undefined;
|
|
189
192
|
|
|
193
|
+
private packetTrailerManager: PacketTrailerManager | undefined;
|
|
194
|
+
|
|
190
195
|
private e2eeStateMutex: Mutex = new Mutex();
|
|
191
196
|
|
|
192
197
|
private connectionReconcileInterval?: ReturnType<typeof setInterval>;
|
|
@@ -307,6 +312,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
307
312
|
this.outgoingDataTrackManager,
|
|
308
313
|
);
|
|
309
314
|
|
|
315
|
+
this.setupPacketTrailer();
|
|
316
|
+
|
|
310
317
|
if (this.options.e2ee || this.options.encryption) {
|
|
311
318
|
this.setupE2EE();
|
|
312
319
|
}
|
|
@@ -465,6 +472,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
465
472
|
}
|
|
466
473
|
}
|
|
467
474
|
|
|
475
|
+
private setupPacketTrailer() {
|
|
476
|
+
// The manager is always created so tracks that advertise packet trailer
|
|
477
|
+
// features can be wired up when the app passes a packet trailer worker.
|
|
478
|
+
this.packetTrailerManager = new PacketTrailerManager(this.options.packetTrailer);
|
|
479
|
+
this.packetTrailerManager.setup(this);
|
|
480
|
+
}
|
|
481
|
+
|
|
468
482
|
private get logContext() {
|
|
469
483
|
return {
|
|
470
484
|
room: this.name,
|
|
@@ -914,6 +928,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
914
928
|
autoSubscribe: connectOptions.autoSubscribe,
|
|
915
929
|
adaptiveStream:
|
|
916
930
|
typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
|
|
931
|
+
clientInfoCapabilities:
|
|
932
|
+
isPacketTrailerSupported(roomOptions.packetTrailer) || !!this.e2eeManager
|
|
933
|
+
? [ClientInfo_Capability.CAP_PACKET_TRAILER]
|
|
934
|
+
: undefined,
|
|
917
935
|
maxRetries: connectOptions.maxRetries,
|
|
918
936
|
e2eeEnabled: !!this.e2eeManager,
|
|
919
937
|
websocketTimeout: connectOptions.websocketTimeout,
|
|
@@ -946,7 +964,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
946
964
|
|
|
947
965
|
if (this.e2eeManager) {
|
|
948
966
|
try {
|
|
949
|
-
this.e2eeManager.setSifTrailer(joinResponse.sifTrailer);
|
|
967
|
+
this.e2eeManager.setSifTrailer(joinResponse.sifTrailer as NonSharedUint8Array);
|
|
950
968
|
} catch (e: any) {
|
|
951
969
|
this.log.error(e instanceof Error ? e.message : 'Could not set SifTrailer', {
|
|
952
970
|
error: e,
|
|
@@ -1825,7 +1843,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1825
1843
|
this.handleParticipantDisconnected(info.identity, remoteParticipant);
|
|
1826
1844
|
} else {
|
|
1827
1845
|
// create participant if doesn't exist
|
|
1828
|
-
|
|
1846
|
+
this.getOrCreateParticipant(info.identity, info);
|
|
1829
1847
|
}
|
|
1830
1848
|
}
|
|
1831
1849
|
|
|
@@ -2018,7 +2036,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2018
2036
|
) => {
|
|
2019
2037
|
this.emit(
|
|
2020
2038
|
RoomEvent.DataReceived,
|
|
2021
|
-
userPacket.payload,
|
|
2039
|
+
userPacket.payload as NonSharedUint8Array,
|
|
2022
2040
|
participant,
|
|
2023
2041
|
kind,
|
|
2024
2042
|
userPacket.topic,
|
|
@@ -2026,7 +2044,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2026
2044
|
);
|
|
2027
2045
|
|
|
2028
2046
|
// also emit on the participant
|
|
2029
|
-
participant?.emit(
|
|
2047
|
+
participant?.emit(
|
|
2048
|
+
ParticipantEvent.DataReceived,
|
|
2049
|
+
userPacket.payload as NonSharedUint8Array,
|
|
2050
|
+
kind,
|
|
2051
|
+
encryptionType,
|
|
2052
|
+
);
|
|
2030
2053
|
};
|
|
2031
2054
|
|
|
2032
2055
|
private handleSipDtmf = (participant: RemoteParticipant | undefined, dtmf: SipDTMF) => {
|
|
@@ -2874,7 +2897,7 @@ export type RoomEventCallbacks = {
|
|
|
2874
2897
|
activeSpeakersChanged: (speakers: Array<Participant>) => void;
|
|
2875
2898
|
roomMetadataChanged: (metadata: string) => void;
|
|
2876
2899
|
dataReceived: (
|
|
2877
|
-
payload:
|
|
2900
|
+
payload: NonSharedUint8Array,
|
|
2878
2901
|
participant?: RemoteParticipant,
|
|
2879
2902
|
kind?: DataPacket_Kind,
|
|
2880
2903
|
topic?: string,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type IRemoteTrack,
|
|
8
8
|
TrackSymbol,
|
|
9
9
|
} from './track-interfaces';
|
|
10
|
-
import { type DataTrackInfo } from './types';
|
|
10
|
+
import { type DataTrackInfo, type RemoteDataTrackPipelineOptions } from './types';
|
|
11
11
|
|
|
12
12
|
type RemoteDataTrackOptions = {
|
|
13
13
|
publisherIdentity: Participant['identity'];
|
|
@@ -80,4 +80,11 @@ export default class RemoteDataTrack implements IRemoteTrack, IDataTrack {
|
|
|
80
80
|
throw err;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
/** Configure how incoming frames for this track are processed before they are handed out to
|
|
85
|
+
* subscribers (the "pipeline"). These options apply to all current and future subscriptions
|
|
86
|
+
* of this track, and may be set at any time. */
|
|
87
|
+
setPipelineOptions(options: RemoteDataTrackPipelineOptions): void {
|
|
88
|
+
this.manager.setPipelineOptions(this.info.sid, options);
|
|
89
|
+
}
|
|
83
90
|
}
|
|
@@ -106,7 +106,7 @@ describe('DataTrackDepacketizer', () => {
|
|
|
106
106
|
new Uint8Array(8),
|
|
107
107
|
);
|
|
108
108
|
|
|
109
|
-
expect(() => depacketizer.push(packetB, {
|
|
109
|
+
expect(() => depacketizer.push(packetB, { throwOnInterruption: true })).toThrowError(
|
|
110
110
|
'Frame 5 dropped: Interrupted by the start of a new frame',
|
|
111
111
|
);
|
|
112
112
|
});
|
|
@@ -439,4 +439,436 @@ describe('DataTrackDepacketizer', () => {
|
|
|
439
439
|
new Uint8Array([0x01, 0x02, 0x03]),
|
|
440
440
|
);
|
|
441
441
|
});
|
|
442
|
+
|
|
443
|
+
it('should assemble multiple partial frames concurrently when maxPartialFrames is set', () => {
|
|
444
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
445
|
+
const pushOptions = { throwOnInterruption: true, maxPartialFrames: 2 };
|
|
446
|
+
|
|
447
|
+
const packetPayload = new Uint8Array(8);
|
|
448
|
+
const baseHeaderParams = {
|
|
449
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
450
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// Begin frame A
|
|
454
|
+
const startA = new DataTrackPacket(
|
|
455
|
+
new DataTrackPacketHeader({
|
|
456
|
+
...baseHeaderParams,
|
|
457
|
+
marker: FrameMarker.Start,
|
|
458
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
459
|
+
frameNumber: WrapAroundUnsignedInt.u16(1),
|
|
460
|
+
}),
|
|
461
|
+
packetPayload,
|
|
462
|
+
);
|
|
463
|
+
expect(depacketizer.push(startA, pushOptions)).toBeNull();
|
|
464
|
+
|
|
465
|
+
// Begin frame B - should not throw because we're under capacity
|
|
466
|
+
const startB = new DataTrackPacket(
|
|
467
|
+
new DataTrackPacketHeader({
|
|
468
|
+
...baseHeaderParams,
|
|
469
|
+
marker: FrameMarker.Start,
|
|
470
|
+
sequence: WrapAroundUnsignedInt.u16(100),
|
|
471
|
+
frameNumber: WrapAroundUnsignedInt.u16(2),
|
|
472
|
+
}),
|
|
473
|
+
packetPayload,
|
|
474
|
+
);
|
|
475
|
+
expect(depacketizer.push(startB, pushOptions)).toBeNull();
|
|
476
|
+
|
|
477
|
+
// Complete frame A out of order - should produce a frame
|
|
478
|
+
const finalA = new DataTrackPacket(
|
|
479
|
+
new DataTrackPacketHeader({
|
|
480
|
+
...baseHeaderParams,
|
|
481
|
+
marker: FrameMarker.Final,
|
|
482
|
+
sequence: WrapAroundUnsignedInt.u16(1),
|
|
483
|
+
frameNumber: WrapAroundUnsignedInt.u16(1),
|
|
484
|
+
}),
|
|
485
|
+
packetPayload,
|
|
486
|
+
);
|
|
487
|
+
const frameA = depacketizer.push(finalA, pushOptions);
|
|
488
|
+
expect(frameA).not.toBeNull();
|
|
489
|
+
expect(frameA!.payload.byteLength).toStrictEqual(packetPayload.byteLength * 2);
|
|
490
|
+
|
|
491
|
+
// Frame B is still in flight and should still complete cleanly
|
|
492
|
+
const finalB = new DataTrackPacket(
|
|
493
|
+
new DataTrackPacketHeader({
|
|
494
|
+
...baseHeaderParams,
|
|
495
|
+
marker: FrameMarker.Final,
|
|
496
|
+
sequence: WrapAroundUnsignedInt.u16(101),
|
|
497
|
+
frameNumber: WrapAroundUnsignedInt.u16(2),
|
|
498
|
+
}),
|
|
499
|
+
packetPayload,
|
|
500
|
+
);
|
|
501
|
+
const frameB = depacketizer.push(finalB, pushOptions);
|
|
502
|
+
expect(frameB).not.toBeNull();
|
|
503
|
+
expect(frameB!.payload.byteLength).toStrictEqual(packetPayload.byteLength * 2);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('should throw when starting a new partial frame would exceed maxPartialFrames', () => {
|
|
507
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
508
|
+
const pushOptions = { throwOnInterruption: true, maxPartialFrames: 2 };
|
|
509
|
+
|
|
510
|
+
const packetPayload = new Uint8Array(8);
|
|
511
|
+
const baseHeaderParams = {
|
|
512
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
513
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
// Fill the partials map with two in-flight frames
|
|
517
|
+
const startA = new DataTrackPacket(
|
|
518
|
+
new DataTrackPacketHeader({
|
|
519
|
+
...baseHeaderParams,
|
|
520
|
+
marker: FrameMarker.Start,
|
|
521
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
522
|
+
frameNumber: WrapAroundUnsignedInt.u16(1),
|
|
523
|
+
}),
|
|
524
|
+
packetPayload,
|
|
525
|
+
);
|
|
526
|
+
expect(depacketizer.push(startA, pushOptions)).toBeNull();
|
|
527
|
+
|
|
528
|
+
const startB = new DataTrackPacket(
|
|
529
|
+
new DataTrackPacketHeader({
|
|
530
|
+
...baseHeaderParams,
|
|
531
|
+
marker: FrameMarker.Start,
|
|
532
|
+
sequence: WrapAroundUnsignedInt.u16(100),
|
|
533
|
+
frameNumber: WrapAroundUnsignedInt.u16(2),
|
|
534
|
+
}),
|
|
535
|
+
packetPayload,
|
|
536
|
+
);
|
|
537
|
+
expect(depacketizer.push(startB, pushOptions)).toBeNull();
|
|
538
|
+
|
|
539
|
+
// A third in-flight start should throw, naming the oldest evicted frame (1) and the new one (3)
|
|
540
|
+
const startC = new DataTrackPacket(
|
|
541
|
+
new DataTrackPacketHeader({
|
|
542
|
+
...baseHeaderParams,
|
|
543
|
+
marker: FrameMarker.Start,
|
|
544
|
+
sequence: WrapAroundUnsignedInt.u16(200),
|
|
545
|
+
frameNumber: WrapAroundUnsignedInt.u16(3),
|
|
546
|
+
}),
|
|
547
|
+
packetPayload,
|
|
548
|
+
);
|
|
549
|
+
expect(() => depacketizer.push(startC, pushOptions)).toThrowError(
|
|
550
|
+
'Frame 1 dropped: Interrupted by the start of a new frame 3',
|
|
551
|
+
);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('should throw when a single-packet frame arrives while the partials map is at capacity', () => {
|
|
555
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
556
|
+
const pushOptions = { throwOnInterruption: true, maxPartialFrames: 2 };
|
|
557
|
+
|
|
558
|
+
const packetPayload = new Uint8Array(8);
|
|
559
|
+
const baseHeaderParams = {
|
|
560
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
561
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
// Fill the partials map with two in-flight frames
|
|
565
|
+
const startA = new DataTrackPacket(
|
|
566
|
+
new DataTrackPacketHeader({
|
|
567
|
+
...baseHeaderParams,
|
|
568
|
+
marker: FrameMarker.Start,
|
|
569
|
+
sequence: WrapAroundUnsignedInt.u16(0),
|
|
570
|
+
frameNumber: WrapAroundUnsignedInt.u16(1),
|
|
571
|
+
}),
|
|
572
|
+
packetPayload,
|
|
573
|
+
);
|
|
574
|
+
expect(depacketizer.push(startA, pushOptions)).toBeNull();
|
|
575
|
+
|
|
576
|
+
const startB = new DataTrackPacket(
|
|
577
|
+
new DataTrackPacketHeader({
|
|
578
|
+
...baseHeaderParams,
|
|
579
|
+
marker: FrameMarker.Start,
|
|
580
|
+
sequence: WrapAroundUnsignedInt.u16(100),
|
|
581
|
+
frameNumber: WrapAroundUnsignedInt.u16(2),
|
|
582
|
+
}),
|
|
583
|
+
packetPayload,
|
|
584
|
+
);
|
|
585
|
+
expect(depacketizer.push(startB, pushOptions)).toBeNull();
|
|
586
|
+
|
|
587
|
+
// A single-packet frame arriving at capacity should evict the oldest (frame 1) and throw
|
|
588
|
+
const singleC = new DataTrackPacket(
|
|
589
|
+
new DataTrackPacketHeader({
|
|
590
|
+
...baseHeaderParams,
|
|
591
|
+
marker: FrameMarker.Single,
|
|
592
|
+
sequence: WrapAroundUnsignedInt.u16(200),
|
|
593
|
+
frameNumber: WrapAroundUnsignedInt.u16(3),
|
|
594
|
+
}),
|
|
595
|
+
packetPayload,
|
|
596
|
+
);
|
|
597
|
+
expect(() => depacketizer.push(singleC, pushOptions)).toThrowError(
|
|
598
|
+
'Frame 1 dropped: Interrupted by the start of a new frame 3',
|
|
599
|
+
);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
it('should evict the oldest partial frame when start packets exceed maxPartialFrames', () => {
|
|
603
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
604
|
+
const pushOptions = { throwOnInterruption: false, maxPartialFrames: 5 };
|
|
605
|
+
const totalFrames = 10;
|
|
606
|
+
|
|
607
|
+
const packetPayload = new Uint8Array(8);
|
|
608
|
+
const baseHeaderParams = {
|
|
609
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
610
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// Begin 10 partial frames. Each frame's Start uses sequence i*2; its Final uses i*2 + 1.
|
|
614
|
+
// After all 10 starts, only frames 6..10 remain in the partials map (oldest evicted first).
|
|
615
|
+
for (let i = 0; i < totalFrames; i += 1) {
|
|
616
|
+
const start = new DataTrackPacket(
|
|
617
|
+
new DataTrackPacketHeader({
|
|
618
|
+
...baseHeaderParams,
|
|
619
|
+
marker: FrameMarker.Start,
|
|
620
|
+
sequence: WrapAroundUnsignedInt.u16(i * 2),
|
|
621
|
+
frameNumber: WrapAroundUnsignedInt.u16(i + 1),
|
|
622
|
+
}),
|
|
623
|
+
packetPayload,
|
|
624
|
+
);
|
|
625
|
+
expect(depacketizer.push(start, pushOptions)).toBeNull();
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Send Final for each frame. Frames 1..5 were evicted → unknownFrame; frames 6..10 produce.
|
|
629
|
+
let producedFrames = 0;
|
|
630
|
+
let unknownFrameErrors = 0;
|
|
631
|
+
for (let i = 0; i < totalFrames; i += 1) {
|
|
632
|
+
const final = new DataTrackPacket(
|
|
633
|
+
new DataTrackPacketHeader({
|
|
634
|
+
...baseHeaderParams,
|
|
635
|
+
marker: FrameMarker.Final,
|
|
636
|
+
sequence: WrapAroundUnsignedInt.u16(i * 2 + 1),
|
|
637
|
+
frameNumber: WrapAroundUnsignedInt.u16(i + 1),
|
|
638
|
+
}),
|
|
639
|
+
packetPayload,
|
|
640
|
+
);
|
|
641
|
+
try {
|
|
642
|
+
const frame = depacketizer.push(final, pushOptions);
|
|
643
|
+
if (frame) {
|
|
644
|
+
producedFrames += 1;
|
|
645
|
+
}
|
|
646
|
+
} catch (err) {
|
|
647
|
+
expect((err as Error).message).toContain('Initial packet was never received.');
|
|
648
|
+
unknownFrameErrors += 1;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
expect(producedFrames).toStrictEqual(5);
|
|
653
|
+
expect(unknownFrameErrors).toStrictEqual(5);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it('should throw unknownFrame for late Inter and Final packets belonging to an evicted frame', () => {
|
|
657
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
658
|
+
const pushOptions = { throwOnInterruption: false, maxPartialFrames: 3 };
|
|
659
|
+
|
|
660
|
+
const packetPayload = new Uint8Array(8);
|
|
661
|
+
const baseHeaderParams = {
|
|
662
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
663
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
// Fill the partials map with three in-flight frames.
|
|
667
|
+
for (let i = 1; i <= 3; i += 1) {
|
|
668
|
+
const start = new DataTrackPacket(
|
|
669
|
+
new DataTrackPacketHeader({
|
|
670
|
+
...baseHeaderParams,
|
|
671
|
+
marker: FrameMarker.Start,
|
|
672
|
+
sequence: WrapAroundUnsignedInt.u16(i * 100),
|
|
673
|
+
frameNumber: WrapAroundUnsignedInt.u16(i),
|
|
674
|
+
}),
|
|
675
|
+
packetPayload,
|
|
676
|
+
);
|
|
677
|
+
expect(depacketizer.push(start, pushOptions)).toBeNull();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// A fourth Start evicts the oldest (frame 1).
|
|
681
|
+
const startFour = new DataTrackPacket(
|
|
682
|
+
new DataTrackPacketHeader({
|
|
683
|
+
...baseHeaderParams,
|
|
684
|
+
marker: FrameMarker.Start,
|
|
685
|
+
sequence: WrapAroundUnsignedInt.u16(400),
|
|
686
|
+
frameNumber: WrapAroundUnsignedInt.u16(4),
|
|
687
|
+
}),
|
|
688
|
+
packetPayload,
|
|
689
|
+
);
|
|
690
|
+
expect(depacketizer.push(startFour, pushOptions)).toBeNull();
|
|
691
|
+
|
|
692
|
+
// A late Inter for the evicted frame 1 should throw unknownFrame.
|
|
693
|
+
const lateInterOne = new DataTrackPacket(
|
|
694
|
+
new DataTrackPacketHeader({
|
|
695
|
+
...baseHeaderParams,
|
|
696
|
+
marker: FrameMarker.Inter,
|
|
697
|
+
sequence: WrapAroundUnsignedInt.u16(101),
|
|
698
|
+
frameNumber: WrapAroundUnsignedInt.u16(1),
|
|
699
|
+
}),
|
|
700
|
+
packetPayload,
|
|
701
|
+
);
|
|
702
|
+
expect(() => depacketizer.push(lateInterOne, pushOptions)).toThrowError(
|
|
703
|
+
'Frame 1 dropped: Initial packet was never received.',
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
// A late Final for the evicted frame 1 should also throw unknownFrame.
|
|
707
|
+
const lateFinalOne = new DataTrackPacket(
|
|
708
|
+
new DataTrackPacketHeader({
|
|
709
|
+
...baseHeaderParams,
|
|
710
|
+
marker: FrameMarker.Final,
|
|
711
|
+
sequence: WrapAroundUnsignedInt.u16(102),
|
|
712
|
+
frameNumber: WrapAroundUnsignedInt.u16(1),
|
|
713
|
+
}),
|
|
714
|
+
packetPayload,
|
|
715
|
+
);
|
|
716
|
+
expect(() => depacketizer.push(lateFinalOne, pushOptions)).toThrowError(
|
|
717
|
+
'Frame 1 dropped: Initial packet was never received.',
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
// Frames 2, 3 and 4 should all still complete cleanly despite the late packets for frame 1.
|
|
721
|
+
for (const frameNumber of [2, 3, 4]) {
|
|
722
|
+
const final = new DataTrackPacket(
|
|
723
|
+
new DataTrackPacketHeader({
|
|
724
|
+
...baseHeaderParams,
|
|
725
|
+
marker: FrameMarker.Final,
|
|
726
|
+
sequence: WrapAroundUnsignedInt.u16(frameNumber * 100 + 1),
|
|
727
|
+
frameNumber: WrapAroundUnsignedInt.u16(frameNumber),
|
|
728
|
+
}),
|
|
729
|
+
packetPayload,
|
|
730
|
+
);
|
|
731
|
+
expect(depacketizer.push(final, pushOptions)).not.toBeNull();
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it('should keep partial frame state isolated when packets for multiple frames are heavily interleaved', () => {
|
|
736
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
737
|
+
const pushOptions = { throwOnInterruption: true, maxPartialFrames: 3 };
|
|
738
|
+
const baseHeaderParams = {
|
|
739
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
740
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
// Three frames each carrying three uniquely-tagged payloads. Sequence ranges are chosen so
|
|
744
|
+
// that no two frames share a sequence value.
|
|
745
|
+
const frames = [
|
|
746
|
+
{
|
|
747
|
+
frameNumber: 1,
|
|
748
|
+
startSequence: 0,
|
|
749
|
+
payloads: [new Uint8Array([0xa1]), new Uint8Array([0xa2]), new Uint8Array([0xa3])],
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
frameNumber: 2,
|
|
753
|
+
startSequence: 100,
|
|
754
|
+
payloads: [new Uint8Array([0xb1]), new Uint8Array([0xb2]), new Uint8Array([0xb3])],
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
frameNumber: 3,
|
|
758
|
+
startSequence: 200,
|
|
759
|
+
payloads: [new Uint8Array([0xc1]), new Uint8Array([0xc2]), new Uint8Array([0xc3])],
|
|
760
|
+
},
|
|
761
|
+
];
|
|
762
|
+
|
|
763
|
+
const buildPacket = (
|
|
764
|
+
frameIndex: number,
|
|
765
|
+
packetIndex: number,
|
|
766
|
+
marker: FrameMarker,
|
|
767
|
+
): DataTrackPacket =>
|
|
768
|
+
new DataTrackPacket(
|
|
769
|
+
new DataTrackPacketHeader({
|
|
770
|
+
...baseHeaderParams,
|
|
771
|
+
marker,
|
|
772
|
+
sequence: WrapAroundUnsignedInt.u16(frames[frameIndex].startSequence + packetIndex),
|
|
773
|
+
frameNumber: WrapAroundUnsignedInt.u16(frames[frameIndex].frameNumber),
|
|
774
|
+
}),
|
|
775
|
+
frames[frameIndex].payloads[packetIndex],
|
|
776
|
+
);
|
|
777
|
+
|
|
778
|
+
// Round-robin Starts and Inters across all three frames.
|
|
779
|
+
expect(depacketizer.push(buildPacket(0, 0, FrameMarker.Start), pushOptions)).toBeNull();
|
|
780
|
+
expect(depacketizer.push(buildPacket(1, 0, FrameMarker.Start), pushOptions)).toBeNull();
|
|
781
|
+
expect(depacketizer.push(buildPacket(2, 0, FrameMarker.Start), pushOptions)).toBeNull();
|
|
782
|
+
expect(depacketizer.push(buildPacket(0, 1, FrameMarker.Inter), pushOptions)).toBeNull();
|
|
783
|
+
expect(depacketizer.push(buildPacket(1, 1, FrameMarker.Inter), pushOptions)).toBeNull();
|
|
784
|
+
expect(depacketizer.push(buildPacket(2, 1, FrameMarker.Inter), pushOptions)).toBeNull();
|
|
785
|
+
|
|
786
|
+
// Finals arrive in a different order than the Starts to confirm per-frame isolation.
|
|
787
|
+
const frameTwo = depacketizer.push(buildPacket(1, 2, FrameMarker.Final), pushOptions);
|
|
788
|
+
expect(frameTwo).not.toBeNull();
|
|
789
|
+
expect(frameTwo!.payload).toStrictEqual(new Uint8Array([0xb1, 0xb2, 0xb3]));
|
|
790
|
+
|
|
791
|
+
const frameZero = depacketizer.push(buildPacket(0, 2, FrameMarker.Final), pushOptions);
|
|
792
|
+
expect(frameZero).not.toBeNull();
|
|
793
|
+
expect(frameZero!.payload).toStrictEqual(new Uint8Array([0xa1, 0xa2, 0xa3]));
|
|
794
|
+
|
|
795
|
+
const frameThree = depacketizer.push(buildPacket(2, 2, FrameMarker.Final), pushOptions);
|
|
796
|
+
expect(frameThree).not.toBeNull();
|
|
797
|
+
expect(frameThree!.payload).toStrictEqual(new Uint8Array([0xc1, 0xc2, 0xc3]));
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
it('should respect maxPartialFrames changing across push calls, both expanding to allow more in-flight frames and shrinking to evict older ones', () => {
|
|
801
|
+
const depacketizer = new DataTrackDepacketizer();
|
|
802
|
+
|
|
803
|
+
const packetPayload = new Uint8Array(8);
|
|
804
|
+
const baseHeaderParams = {
|
|
805
|
+
trackHandle: DataTrackHandle.fromNumber(101),
|
|
806
|
+
timestamp: DataTrackTimestamp.fromRtpTicks(104),
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
const startFor = (frameNumber: number) =>
|
|
810
|
+
new DataTrackPacket(
|
|
811
|
+
new DataTrackPacketHeader({
|
|
812
|
+
...baseHeaderParams,
|
|
813
|
+
marker: FrameMarker.Start,
|
|
814
|
+
sequence: WrapAroundUnsignedInt.u16(frameNumber * 100),
|
|
815
|
+
frameNumber: WrapAroundUnsignedInt.u16(frameNumber),
|
|
816
|
+
}),
|
|
817
|
+
packetPayload,
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
const finalFor = (frameNumber: number) =>
|
|
821
|
+
new DataTrackPacket(
|
|
822
|
+
new DataTrackPacketHeader({
|
|
823
|
+
...baseHeaderParams,
|
|
824
|
+
marker: FrameMarker.Final,
|
|
825
|
+
sequence: WrapAroundUnsignedInt.u16(frameNumber * 100 + 1),
|
|
826
|
+
frameNumber: WrapAroundUnsignedInt.u16(frameNumber),
|
|
827
|
+
}),
|
|
828
|
+
packetPayload,
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
// Fill the partials map exactly with maxPartialFrames=2.
|
|
832
|
+
expect(
|
|
833
|
+
depacketizer.push(startFor(1), { throwOnInterruption: true, maxPartialFrames: 2 }),
|
|
834
|
+
).toBeNull();
|
|
835
|
+
expect(
|
|
836
|
+
depacketizer.push(startFor(2), { throwOnInterruption: true, maxPartialFrames: 2 }),
|
|
837
|
+
).toBeNull();
|
|
838
|
+
|
|
839
|
+
// Expand maxPartialFrames to 4 mid-stream. Frames 3 and 4 should be added without evicting
|
|
840
|
+
// anything, and throwOnInterruption: true confirms no interruption fires.
|
|
841
|
+
expect(
|
|
842
|
+
depacketizer.push(startFor(3), { throwOnInterruption: true, maxPartialFrames: 4 }),
|
|
843
|
+
).toBeNull();
|
|
844
|
+
expect(
|
|
845
|
+
depacketizer.push(startFor(4), { throwOnInterruption: true, maxPartialFrames: 4 }),
|
|
846
|
+
).toBeNull();
|
|
847
|
+
|
|
848
|
+
// Spot-check that frame 1 is still tracked despite the cap changes.
|
|
849
|
+
expect(
|
|
850
|
+
depacketizer.push(finalFor(1), { throwOnInterruption: true, maxPartialFrames: 4 }),
|
|
851
|
+
).not.toBeNull();
|
|
852
|
+
// Three partials remain in flight: frames 2, 3, 4.
|
|
853
|
+
|
|
854
|
+
// Shrink maxPartialFrames to 2. Adding frame 5 should evict frames 2 and 3 in this single
|
|
855
|
+
// push call to bring the in-flight count back under the new cap.
|
|
856
|
+
expect(
|
|
857
|
+
depacketizer.push(startFor(5), { throwOnInterruption: false, maxPartialFrames: 2 }),
|
|
858
|
+
).toBeNull();
|
|
859
|
+
|
|
860
|
+
// Only frames 4 and 5 should remain in the map.
|
|
861
|
+
expect(() =>
|
|
862
|
+
depacketizer.push(finalFor(2), { throwOnInterruption: false, maxPartialFrames: 2 }),
|
|
863
|
+
).toThrowError('Frame 2 dropped: Initial packet was never received.');
|
|
864
|
+
expect(() =>
|
|
865
|
+
depacketizer.push(finalFor(3), { throwOnInterruption: false, maxPartialFrames: 2 }),
|
|
866
|
+
).toThrowError('Frame 3 dropped: Initial packet was never received.');
|
|
867
|
+
expect(
|
|
868
|
+
depacketizer.push(finalFor(4), { throwOnInterruption: false, maxPartialFrames: 2 }),
|
|
869
|
+
).not.toBeNull();
|
|
870
|
+
expect(
|
|
871
|
+
depacketizer.push(finalFor(5), { throwOnInterruption: false, maxPartialFrames: 2 }),
|
|
872
|
+
).not.toBeNull();
|
|
873
|
+
});
|
|
442
874
|
});
|