livekit-client 2.18.9 → 2.19.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/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 +3553 -2813
- 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 -4
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +7 -3
- 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 +8 -14
- 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/RemoteParticipant.d.ts +5 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/rpc/client/RpcClientManager.d.ts +39 -0
- package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -0
- package/dist/src/room/rpc/client/events.d.ts +8 -0
- package/dist/src/room/rpc/client/events.d.ts.map +1 -0
- package/dist/src/room/rpc/index.d.ts +6 -0
- package/dist/src/room/rpc/index.d.ts.map +1 -0
- package/dist/src/room/rpc/server/RpcServerManager.d.ts +44 -0
- package/dist/src/room/rpc/server/RpcServerManager.d.ts.map +1 -0
- package/dist/src/room/rpc/server/events.d.ts +8 -0
- package/dist/src/room/rpc/server/events.d.ts.map +1 -0
- package/dist/src/room/{rpc.d.ts → rpc/utils.d.ts} +34 -4
- package/dist/src/room/rpc/utils.d.ts.map +1 -0
- 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 +9 -1
- package/dist/src/version.d.ts.map +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 -4
- package/dist/ts4.2/room/Room.d.ts +7 -3
- 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 +8 -14
- package/dist/ts4.2/room/participant/Participant.d.ts +1 -1
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +5 -1
- package/dist/ts4.2/room/rpc/client/RpcClientManager.d.ts +43 -0
- package/dist/ts4.2/room/rpc/client/events.d.ts +8 -0
- package/dist/ts4.2/room/rpc/index.d.ts +7 -0
- package/dist/ts4.2/room/rpc/server/RpcServerManager.d.ts +44 -0
- package/dist/ts4.2/room/rpc/server/events.d.ts +8 -0
- package/dist/ts4.2/room/{rpc.d.ts → rpc/utils.d.ts} +34 -4
- 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 +9 -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 -46
- package/src/room/Room.test.ts +62 -1
- package/src/room/Room.ts +111 -86
- 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 +64 -187
- package/src/room/participant/Participant.ts +1 -1
- package/src/room/participant/RemoteParticipant.ts +9 -0
- package/src/room/participant/publishUtils.ts +1 -1
- package/src/room/rpc/client/RpcClientManager.test.ts +430 -0
- package/src/room/rpc/client/RpcClientManager.ts +269 -0
- package/src/room/rpc/client/events.ts +9 -0
- package/src/room/rpc/index.ts +14 -0
- package/src/room/rpc/server/RpcServerManager.test.ts +471 -0
- package/src/room/rpc/server/RpcServerManager.ts +293 -0
- package/src/room/rpc/server/events.ts +9 -0
- package/src/room/{rpc.ts → rpc/utils.ts} +49 -8
- 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 +19 -4
- 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 +11 -1
- package/dist/src/room/rpc.d.ts.map +0 -1
- package/src/room/rpc.test.ts +0 -301
|
@@ -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
|
});
|
|
@@ -9,14 +9,12 @@ import { U16_MAX_SIZE, WrapAroundUnsignedInt } from './utils';
|
|
|
9
9
|
const log = getLogger(LoggerNames.DataTracks);
|
|
10
10
|
|
|
11
11
|
type PartialFrame = {
|
|
12
|
-
/** Frame number from the start packet. */
|
|
13
|
-
frameNumber: number;
|
|
14
12
|
/** Sequence of the start packet. */
|
|
15
13
|
startSequence: WrapAroundUnsignedInt<typeof U16_MAX_SIZE>;
|
|
16
14
|
/** Extensions from the start packet. */
|
|
17
15
|
extensions: DataTrackExtensions;
|
|
18
16
|
/** Mapping between sequence number and packet payload. */
|
|
19
|
-
payloads: Map<number,
|
|
17
|
+
payloads: Map<number, NonSharedUint8Array>;
|
|
20
18
|
};
|
|
21
19
|
|
|
22
20
|
/** An error indicating a frame was dropped. */
|
|
@@ -80,16 +78,24 @@ export enum DataTrackDepacketizerDropReason {
|
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
type PushOptions = {
|
|
83
|
-
/** If true, throws
|
|
84
|
-
*
|
|
85
|
-
|
|
81
|
+
/** If true, throws `DataTrackDepacketizerDropError.interrupted` instead of logging a warning
|
|
82
|
+
* when a new frame arrives while the partials map is at capacity. */
|
|
83
|
+
throwOnInterruption: boolean;
|
|
84
|
+
|
|
85
|
+
/** Maximum number of partial frames the depacketizer will track concurrently. When a new
|
|
86
|
+
* frame arrives while the partials map is at capacity, the oldest partial is evicted (or
|
|
87
|
+
* `DataTrackDepacketizerDropError.interrupted` is thrown when `throwOnInterruption` is set).
|
|
88
|
+
* Defaults to 1. */
|
|
89
|
+
maxPartialFrames?: number;
|
|
86
90
|
};
|
|
87
91
|
|
|
88
92
|
export default class DataTrackDepacketizer {
|
|
89
93
|
/** Maximum number of packets to buffer per frame before dropping. */
|
|
90
94
|
static MAX_BUFFER_PACKETS = 128;
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
/** Partial frames currently being assembled, keyed by frame number. `Map` preserves insertion
|
|
97
|
+
* order, so the oldest entry is the first key. */
|
|
98
|
+
private partials: Map<number, PartialFrame> = new Map();
|
|
93
99
|
|
|
94
100
|
/** Should be repeatedly called with received {@link DataTrackPacket}s - intermediate calls
|
|
95
101
|
* aggregate the packet's state internally, and return null.
|
|
@@ -112,14 +118,19 @@ export default class DataTrackDepacketizer {
|
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
reset() {
|
|
115
|
-
this.
|
|
121
|
+
this.partials.clear();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private peekOldestPartialFrameNumber(): number | null {
|
|
125
|
+
const first = this.partials.keys().next();
|
|
126
|
+
return first.done ? null : first.value;
|
|
116
127
|
}
|
|
117
128
|
|
|
118
129
|
private frameFromSingle(
|
|
119
130
|
packet: DataTrackPacket,
|
|
120
131
|
options?: PushOptions,
|
|
121
132
|
): Throws<
|
|
122
|
-
DataTrackFrameInternal
|
|
133
|
+
DataTrackFrameInternal,
|
|
123
134
|
DataTrackDepacketizerDropError<DataTrackDepacketizerDropReason.Interrupted>
|
|
124
135
|
> {
|
|
125
136
|
if (packet.header.marker !== FrameMarker.Single) {
|
|
@@ -129,21 +140,30 @@ export default class DataTrackDepacketizer {
|
|
|
129
140
|
);
|
|
130
141
|
}
|
|
131
142
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
143
|
+
// A `Single` packet is a self-contained frame and doesn't reserve a partials slot, but if
|
|
144
|
+
// the partials map is at capacity, treat it as a signal that the oldest in-flight partial
|
|
145
|
+
// is stale and evict it (matches `main`'s behavior when `maxPartialFrames`
|
|
146
|
+
// defaults to 1).
|
|
147
|
+
const maxPartialFrames = options?.maxPartialFrames ?? 1;
|
|
148
|
+
if (this.partials.size >= maxPartialFrames) {
|
|
149
|
+
const oldestPartialFrameNumber = this.peekOldestPartialFrameNumber();
|
|
150
|
+
if (typeof oldestPartialFrameNumber !== 'number') {
|
|
151
|
+
// @throws-transformer ignore - this should be treated as a "panic" and not be caught
|
|
152
|
+
throw new Error(
|
|
153
|
+
`Depacketizer.frameFromSingle: no oldest frame number found, but partials.size is ${this.partials.size}.`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
this.partials.delete(oldestPartialFrameNumber);
|
|
157
|
+
if (options?.throwOnInterruption) {
|
|
136
158
|
throw DataTrackDepacketizerDropError.interrupted(
|
|
137
|
-
|
|
159
|
+
oldestPartialFrameNumber,
|
|
138
160
|
packet.header.frameNumber.value,
|
|
139
161
|
);
|
|
140
|
-
} else {
|
|
141
|
-
log.warn(
|
|
142
|
-
`Data track frame ${this.partial.frameNumber} was interrupted by the start of a new frame, dropping.`,
|
|
143
|
-
);
|
|
144
162
|
}
|
|
163
|
+
log.warn(
|
|
164
|
+
`Data track frame ${oldestPartialFrameNumber} was interrupted by single-packet frame ${packet.header.frameNumber.value}, dropping.`,
|
|
165
|
+
);
|
|
145
166
|
}
|
|
146
|
-
this.reset();
|
|
147
167
|
|
|
148
168
|
return { payload: packet.payload, extensions: packet.header.extensions };
|
|
149
169
|
}
|
|
@@ -160,31 +180,36 @@ export default class DataTrackDepacketizer {
|
|
|
160
180
|
);
|
|
161
181
|
}
|
|
162
182
|
|
|
163
|
-
if (this.partial) {
|
|
164
|
-
if (options?.errorOnPartialFrames) {
|
|
165
|
-
const frameNumber = this.partial.frameNumber;
|
|
166
|
-
this.reset();
|
|
167
|
-
throw DataTrackDepacketizerDropError.interrupted(
|
|
168
|
-
frameNumber,
|
|
169
|
-
packet.header.frameNumber.value,
|
|
170
|
-
);
|
|
171
|
-
} else {
|
|
172
|
-
log.warn(
|
|
173
|
-
`Data track frame ${this.partial.frameNumber} was interrupted by the start of a new frame ${packet.header.frameNumber.value}, dropping.`,
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
this.reset();
|
|
178
|
-
|
|
179
183
|
const startSequence = packet.header.sequence;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
frameNumber: packet.header.frameNumber.value,
|
|
184
|
+
const frameNumber = packet.header.frameNumber.value;
|
|
185
|
+
const partial: PartialFrame = {
|
|
183
186
|
startSequence,
|
|
184
187
|
extensions: packet.header.extensions,
|
|
185
188
|
payloads: new Map([[startSequence.value, packet.payload]]),
|
|
186
189
|
};
|
|
187
190
|
|
|
191
|
+
// Loop in case `maxPartialFrames` shrunk relative to a previous push call - evict the
|
|
192
|
+
// oldest partials until there is room for the new one. With `throwOnInterruption` set the
|
|
193
|
+
// throw inside the loop short-circuits on the first eviction, matching the single-eviction
|
|
194
|
+
// behavior callers expect when they ask to be told about interruptions.
|
|
195
|
+
const maxPartialFrames = options?.maxPartialFrames ?? 1;
|
|
196
|
+
while (this.partials.size >= maxPartialFrames) {
|
|
197
|
+
const oldestPartialFrameNumber = this.peekOldestPartialFrameNumber();
|
|
198
|
+
if (typeof oldestPartialFrameNumber !== 'number') {
|
|
199
|
+
// partials map is empty - nothing more to evict
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
this.partials.delete(oldestPartialFrameNumber);
|
|
203
|
+
|
|
204
|
+
if (options?.throwOnInterruption) {
|
|
205
|
+
throw DataTrackDepacketizerDropError.interrupted(oldestPartialFrameNumber, frameNumber);
|
|
206
|
+
}
|
|
207
|
+
log.warn(
|
|
208
|
+
`Data track partials full (max ${maxPartialFrames}), evicted oldest frame ${oldestPartialFrameNumber} to make room for new frame ${frameNumber}.`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
this.partials.set(frameNumber, partial);
|
|
212
|
+
|
|
188
213
|
return null;
|
|
189
214
|
}
|
|
190
215
|
|
|
@@ -199,40 +224,32 @@ export default class DataTrackDepacketizer {
|
|
|
199
224
|
);
|
|
200
225
|
}
|
|
201
226
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (packet.header.frameNumber.value !== this.partial.frameNumber) {
|
|
208
|
-
const frameNumber = this.partial.frameNumber;
|
|
209
|
-
this.reset();
|
|
210
|
-
throw DataTrackDepacketizerDropError.interrupted(
|
|
211
|
-
frameNumber,
|
|
212
|
-
packet.header.frameNumber.value,
|
|
213
|
-
);
|
|
227
|
+
const packetFrameNumber = packet.header.frameNumber.value;
|
|
228
|
+
const matchingPartial = this.partials.get(packetFrameNumber);
|
|
229
|
+
if (!matchingPartial) {
|
|
230
|
+
this.partials.delete(packetFrameNumber);
|
|
231
|
+
throw DataTrackDepacketizerDropError.unknownFrame(packetFrameNumber);
|
|
214
232
|
}
|
|
215
233
|
|
|
216
234
|
// NOTE: this check will block reprocessing packets with duplicate sequence values if the
|
|
217
235
|
// buffer is full already, which could maybe be problematic for very large frames.
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
throw DataTrackDepacketizerDropError.bufferFull(frameNumber);
|
|
236
|
+
if (matchingPartial.payloads.size >= DataTrackDepacketizer.MAX_BUFFER_PACKETS) {
|
|
237
|
+
this.partials.delete(packetFrameNumber);
|
|
238
|
+
throw DataTrackDepacketizerDropError.bufferFull(packetFrameNumber);
|
|
222
239
|
}
|
|
223
240
|
|
|
224
241
|
// Note: receiving a packet with a duplicate `sequence` value is something that likely won't
|
|
225
242
|
// happen in actual use, but even if it does (maybe a low level network retransmission?) the
|
|
226
243
|
// last packet with a given sequence received should always win.
|
|
227
|
-
if (
|
|
244
|
+
if (matchingPartial.payloads.has(packet.header.sequence.value)) {
|
|
228
245
|
log.warn(
|
|
229
|
-
`Data track frame ${
|
|
246
|
+
`Data track frame ${packetFrameNumber} received duplicate packet for sequence ${packet.header.sequence.value}, so replacing with newly received packet.`,
|
|
230
247
|
);
|
|
231
248
|
}
|
|
232
|
-
|
|
249
|
+
matchingPartial.payloads.set(packet.header.sequence.value, packet.payload);
|
|
233
250
|
|
|
234
251
|
if (packet.header.marker === FrameMarker.Final) {
|
|
235
|
-
return this.finalize(
|
|
252
|
+
return this.finalize(packetFrameNumber, matchingPartial, packet.header.sequence.value);
|
|
236
253
|
}
|
|
237
254
|
|
|
238
255
|
return null;
|
|
@@ -240,6 +257,7 @@ export default class DataTrackDepacketizer {
|
|
|
240
257
|
|
|
241
258
|
/** Try to reassemble the complete frame. */
|
|
242
259
|
private finalize(
|
|
260
|
+
partialFrameNumber: number,
|
|
243
261
|
partial: PartialFrame,
|
|
244
262
|
endSequence: number,
|
|
245
263
|
): Throws<
|
|
@@ -281,13 +299,13 @@ export default class DataTrackDepacketizer {
|
|
|
281
299
|
}
|
|
282
300
|
|
|
283
301
|
// The packet is done processing, reset the state so another frame can be processed next.
|
|
284
|
-
this.
|
|
302
|
+
this.partials.delete(partialFrameNumber);
|
|
285
303
|
return { payload, extensions: partial.extensions };
|
|
286
304
|
}
|
|
287
305
|
|
|
288
|
-
this.
|
|
306
|
+
this.partials.delete(partialFrameNumber);
|
|
289
307
|
throw DataTrackDepacketizerDropError.incomplete(
|
|
290
|
-
|
|
308
|
+
partialFrameNumber,
|
|
291
309
|
received,
|
|
292
310
|
endSequence - partial.startSequence.value + 1,
|
|
293
311
|
);
|