livekit-client 2.18.8 → 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 +2898 -2431
- 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/LocalDataTrack.d.ts +2 -1
- package/dist/src/room/data-track/LocalDataTrack.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/OutgoingDataTrackManager.d.ts +2 -2
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/types.d.ts +4 -3
- 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/LocalDataTrack.d.ts +2 -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/OutgoingDataTrackManager.d.ts +2 -2
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +4 -3
- 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/LocalDataTrack.ts +15 -7
- 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/OutgoingDataTrackManager.test.ts +350 -198
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +9 -3
- package/src/room/data-track/outgoing/types.ts +4 -3
- 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
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
1
|
// TODO code inspired by https://github.com/webrtc/samples/blob/gh-pages/src/content/insertable-streams/endtoend-encryption/js/worker.js
|
|
3
2
|
import { EventEmitter } from 'events';
|
|
4
3
|
import type TypedEventEmitter from 'typed-emitter';
|
|
5
4
|
import { workerLogger } from '../../logger';
|
|
5
|
+
import {
|
|
6
|
+
appendPacketTrailerToEncodedFrame,
|
|
7
|
+
processPacketTrailer,
|
|
8
|
+
} from '../../packetTrailer/packetTrailer';
|
|
9
|
+
import type { PacketTrailerPublishOptions } from '../../packetTrailer/types';
|
|
10
|
+
import { hasPacketTrailerPublishOptions } from '../../packetTrailer/utils';
|
|
6
11
|
import type { VideoCodec } from '../../room/track/options';
|
|
7
12
|
import { ENCRYPTION_ALGORITHM, IV_LENGTH, UNENCRYPTED_BYTES } from '../constants';
|
|
8
13
|
import { CryptorError, CryptorErrorReason } from '../errors';
|
|
9
14
|
import { type CryptorCallbacks, CryptorEvent } from '../events';
|
|
10
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
DecodeRatchetOptions,
|
|
17
|
+
KeyProviderOptions,
|
|
18
|
+
KeySet,
|
|
19
|
+
PTMetadataFromE2EEMessage,
|
|
20
|
+
RatchetResult,
|
|
21
|
+
} from '../types';
|
|
11
22
|
import { deriveKeys, isVideoFrame, needsRbspUnescaping, parseRbsp, writeRbsp } from '../utils';
|
|
12
23
|
import type { ParticipantKeyHandler } from './ParticipantKeyHandler';
|
|
13
24
|
import { processNALUsForEncryption } from './naluUtils';
|
|
@@ -65,12 +76,23 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
65
76
|
/**
|
|
66
77
|
* used for detecting server injected unencrypted frames
|
|
67
78
|
*/
|
|
68
|
-
private sifTrailer:
|
|
79
|
+
private sifTrailer: NonSharedUint8Array;
|
|
69
80
|
|
|
70
81
|
private detectedCodec?: VideoCodec;
|
|
71
82
|
|
|
72
83
|
private currentTransform?: TransformerInfo;
|
|
73
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Whether the subscribed track advertises packet trailer features.
|
|
87
|
+
* When false, we skip the per-frame trailer extraction path entirely
|
|
88
|
+
* on decode to avoid unnecessary work on tracks that don't use it.
|
|
89
|
+
*/
|
|
90
|
+
private hasPacketTrailer: boolean = false;
|
|
91
|
+
|
|
92
|
+
private packetTrailer?: PacketTrailerPublishOptions;
|
|
93
|
+
|
|
94
|
+
private packetTrailerFrameId = 0;
|
|
95
|
+
|
|
74
96
|
/**
|
|
75
97
|
* Throttling mechanism for decryption errors to prevent memory leaks
|
|
76
98
|
*/
|
|
@@ -88,7 +110,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
88
110
|
keys: ParticipantKeyHandler;
|
|
89
111
|
participantIdentity: string;
|
|
90
112
|
keyProviderOptions: KeyProviderOptions;
|
|
91
|
-
sifTrailer?:
|
|
113
|
+
sifTrailer?: NonSharedUint8Array;
|
|
92
114
|
}) {
|
|
93
115
|
super();
|
|
94
116
|
this.sendCounts = new Map();
|
|
@@ -178,6 +200,20 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
178
200
|
this.rtpMap = map;
|
|
179
201
|
}
|
|
180
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Sets whether the track associated with this cryptor carries packet
|
|
205
|
+
* trailer data. When false, {@link decodeFunction} skips the per-frame
|
|
206
|
+
* trailer extraction branch entirely.
|
|
207
|
+
*/
|
|
208
|
+
setHasPacketTrailer(hasPacketTrailer: boolean) {
|
|
209
|
+
this.hasPacketTrailer = hasPacketTrailer;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
setPacketTrailer(packetTrailer?: PacketTrailerPublishOptions) {
|
|
213
|
+
this.packetTrailer = packetTrailer;
|
|
214
|
+
this.packetTrailerFrameId = 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
181
217
|
setupTransform(
|
|
182
218
|
operation: 'encode' | 'decode',
|
|
183
219
|
readable: ReadableStream<RTCEncodedVideoFrame | RTCEncodedAudioFrame>,
|
|
@@ -185,11 +221,15 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
185
221
|
trackId: string,
|
|
186
222
|
isReuse: boolean,
|
|
187
223
|
codec?: VideoCodec,
|
|
224
|
+
packetTrailer?: PacketTrailerPublishOptions,
|
|
188
225
|
) {
|
|
189
226
|
if (codec) {
|
|
190
227
|
workerLogger.info('setting codec on cryptor to', { codec });
|
|
191
228
|
this.videoCodec = codec;
|
|
192
229
|
}
|
|
230
|
+
if (operation === 'encode') {
|
|
231
|
+
this.setPacketTrailer(packetTrailer);
|
|
232
|
+
}
|
|
193
233
|
|
|
194
234
|
workerLogger.debug('Setting up frame cryptor transform', {
|
|
195
235
|
operation,
|
|
@@ -262,7 +302,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
262
302
|
});
|
|
263
303
|
}
|
|
264
304
|
|
|
265
|
-
setSifTrailer(trailer:
|
|
305
|
+
setSifTrailer(trailer: NonSharedUint8Array) {
|
|
266
306
|
workerLogger.debug('setting SIF trailer', { ...this.logContext, trailer });
|
|
267
307
|
this.sifTrailer = trailer;
|
|
268
308
|
}
|
|
@@ -353,11 +393,13 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
353
393
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
|
354
394
|
controller: TransformStreamDefaultController,
|
|
355
395
|
) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
396
|
+
// skip for encryption and packet trailer writes for empty dtx frames
|
|
397
|
+
if (encodedFrame.data.byteLength === 0) {
|
|
398
|
+
return controller.enqueue(encodedFrame);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (!this.isEnabled()) {
|
|
402
|
+
this.appendPacketTrailer(encodedFrame);
|
|
361
403
|
return controller.enqueue(encodedFrame);
|
|
362
404
|
}
|
|
363
405
|
const keySet = this.keys.getKeySet();
|
|
@@ -410,7 +452,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
410
452
|
new Uint8Array(encodedFrame.data, frameInfo.unencryptedBytes),
|
|
411
453
|
);
|
|
412
454
|
|
|
413
|
-
let newDataWithoutHeader = new Uint8Array(
|
|
455
|
+
let newDataWithoutHeader: NonSharedUint8Array = new Uint8Array(
|
|
414
456
|
cipherText.byteLength + iv.byteLength + frameTrailer.byteLength,
|
|
415
457
|
);
|
|
416
458
|
newDataWithoutHeader.set(new Uint8Array(cipherText)); // add ciphertext.
|
|
@@ -426,6 +468,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
426
468
|
newData.set(newDataWithoutHeader, frameHeader.byteLength);
|
|
427
469
|
|
|
428
470
|
encodedFrame.data = newData.buffer;
|
|
471
|
+
this.appendPacketTrailer(encodedFrame);
|
|
429
472
|
|
|
430
473
|
return controller.enqueue(encodedFrame);
|
|
431
474
|
} catch (e: any) {
|
|
@@ -444,6 +487,18 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
444
487
|
}
|
|
445
488
|
}
|
|
446
489
|
|
|
490
|
+
private appendPacketTrailer(encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame) {
|
|
491
|
+
if (!hasPacketTrailerPublishOptions(this.packetTrailer) || !isVideoFrame(encodedFrame)) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (this.packetTrailer?.frameId) {
|
|
496
|
+
this.packetTrailerFrameId =
|
|
497
|
+
this.packetTrailerFrameId === 0xffffffff ? 1 : this.packetTrailerFrameId + 1;
|
|
498
|
+
}
|
|
499
|
+
appendPacketTrailerToEncodedFrame(encodedFrame, this.packetTrailer, this.packetTrailerFrameId);
|
|
500
|
+
}
|
|
501
|
+
|
|
447
502
|
/**
|
|
448
503
|
* Function that will be injected in a stream and will decrypt the given encoded frames.
|
|
449
504
|
*
|
|
@@ -454,6 +509,24 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
454
509
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
|
455
510
|
controller: TransformStreamDefaultController,
|
|
456
511
|
) {
|
|
512
|
+
if (this.hasPacketTrailer && isVideoFrame(encodedFrame)) {
|
|
513
|
+
try {
|
|
514
|
+
const ptResult = processPacketTrailer(encodedFrame, this.trackId);
|
|
515
|
+
if (ptResult.data) {
|
|
516
|
+
encodedFrame.data = ptResult.data;
|
|
517
|
+
}
|
|
518
|
+
if (ptResult.payload && this.participantIdentity) {
|
|
519
|
+
const msg: PTMetadataFromE2EEMessage = {
|
|
520
|
+
kind: 'packetTrailerMetadata',
|
|
521
|
+
data: ptResult.payload,
|
|
522
|
+
};
|
|
523
|
+
postMessage(msg);
|
|
524
|
+
}
|
|
525
|
+
} catch {
|
|
526
|
+
// best-effort: never break the media pipeline if trailer parsing fails
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
457
530
|
if (
|
|
458
531
|
!this.isEnabled() ||
|
|
459
532
|
// skip for decryption for empty dtx frames
|
|
@@ -540,8 +613,12 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
540
613
|
// ---------+-------------------------+-+---------+----
|
|
541
614
|
|
|
542
615
|
try {
|
|
543
|
-
const frameHeader = new Uint8Array(
|
|
544
|
-
|
|
616
|
+
const frameHeader: NonSharedUint8Array = new Uint8Array(
|
|
617
|
+
encodedFrame.data,
|
|
618
|
+
0,
|
|
619
|
+
frameInfo.unencryptedBytes,
|
|
620
|
+
);
|
|
621
|
+
var encryptedData: NonSharedUint8Array = new Uint8Array(
|
|
545
622
|
encodedFrame.data,
|
|
546
623
|
frameHeader.length,
|
|
547
624
|
encodedFrame.data.byteLength - frameHeader.length,
|
|
@@ -758,7 +835,10 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
758
835
|
* by the livekit server and thus to be treated as unencrypted
|
|
759
836
|
* @internal
|
|
760
837
|
*/
|
|
761
|
-
export function isFrameServerInjected(
|
|
838
|
+
export function isFrameServerInjected(
|
|
839
|
+
frameData: ArrayBuffer,
|
|
840
|
+
trailerBytes: NonSharedUint8Array,
|
|
841
|
+
): boolean {
|
|
762
842
|
if (trailerBytes.byteLength === 0) {
|
|
763
843
|
return false;
|
|
764
844
|
}
|
|
@@ -261,7 +261,7 @@ describe('ParticipantKeyHandler', () => {
|
|
|
261
261
|
|
|
262
262
|
await keyHandler.setKey(originalMaterial);
|
|
263
263
|
|
|
264
|
-
const ciphertexts:
|
|
264
|
+
const ciphertexts: NonSharedUint8Array[] = [];
|
|
265
265
|
|
|
266
266
|
const plaintext = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
|
|
267
267
|
|
|
@@ -354,8 +354,8 @@ describe('ParticipantKeyHandler', () => {
|
|
|
354
354
|
async function encrypt(
|
|
355
355
|
participantKeyHandler: ParticipantKeyHandler,
|
|
356
356
|
keyIndex: number,
|
|
357
|
-
iv:
|
|
358
|
-
data:
|
|
357
|
+
iv: NonSharedUint8Array,
|
|
358
|
+
data: NonSharedUint8Array,
|
|
359
359
|
): Promise<ArrayBuffer> {
|
|
360
360
|
return crypto.subtle.encrypt(
|
|
361
361
|
{
|
|
@@ -370,7 +370,7 @@ describe('ParticipantKeyHandler', () => {
|
|
|
370
370
|
async function decrypt(
|
|
371
371
|
participantKeyHandler: ParticipantKeyHandler,
|
|
372
372
|
keyIndex: number,
|
|
373
|
-
iv:
|
|
373
|
+
iv: NonSharedUint8Array,
|
|
374
374
|
cipherText: ArrayBuffer,
|
|
375
375
|
): Promise<ArrayBuffer> {
|
|
376
376
|
return crypto.subtle.decrypt(
|
|
@@ -29,7 +29,7 @@ let isEncryptionEnabled: boolean = false;
|
|
|
29
29
|
|
|
30
30
|
let useSharedKey: boolean = false;
|
|
31
31
|
|
|
32
|
-
let sifTrailer:
|
|
32
|
+
let sifTrailer: NonSharedUint8Array | undefined;
|
|
33
33
|
|
|
34
34
|
let keyProviderOptions: KeyProviderOptions = KEY_PROVIDER_DEFAULTS;
|
|
35
35
|
|
|
@@ -64,6 +64,7 @@ onmessage = (ev) => {
|
|
|
64
64
|
break;
|
|
65
65
|
case 'decode':
|
|
66
66
|
let cryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
|
67
|
+
cryptor.setHasPacketTrailer(data.hasPacketTrailer);
|
|
67
68
|
cryptor.setupTransform(
|
|
68
69
|
kind,
|
|
69
70
|
data.readableStream,
|
|
@@ -75,6 +76,7 @@ onmessage = (ev) => {
|
|
|
75
76
|
break;
|
|
76
77
|
case 'encode':
|
|
77
78
|
let pubCryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
|
79
|
+
pubCryptor.setHasPacketTrailer(data.hasPacketTrailer);
|
|
78
80
|
pubCryptor.setupTransform(
|
|
79
81
|
kind,
|
|
80
82
|
data.readableStream,
|
|
@@ -82,6 +84,7 @@ onmessage = (ev) => {
|
|
|
82
84
|
data.trackId,
|
|
83
85
|
data.isReuse,
|
|
84
86
|
data.codec,
|
|
87
|
+
data.packetTrailer,
|
|
85
88
|
);
|
|
86
89
|
break;
|
|
87
90
|
|
|
@@ -159,11 +162,14 @@ onmessage = (ev) => {
|
|
|
159
162
|
unsetCryptorParticipant(data.trackId, data.participantIdentity);
|
|
160
163
|
break;
|
|
161
164
|
case 'updateCodec':
|
|
162
|
-
getTrackCryptor(data.participantIdentity, data.trackId)
|
|
165
|
+
const trackCryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
|
166
|
+
trackCryptor.setVideoCodec(data.codec);
|
|
167
|
+
trackCryptor.setHasPacketTrailer(data.hasPacketTrailer);
|
|
163
168
|
workerLogger.info('updated codec', {
|
|
164
169
|
participantIdentity: data.participantIdentity,
|
|
165
170
|
trackId: data.trackId,
|
|
166
171
|
codec: data.codec,
|
|
172
|
+
hasPacketTrailer: data.hasPacketTrailer,
|
|
167
173
|
});
|
|
168
174
|
break;
|
|
169
175
|
case 'setRTPMap':
|
|
@@ -319,7 +325,7 @@ function emitRatchetedKeys(
|
|
|
319
325
|
postMessage(msg);
|
|
320
326
|
}
|
|
321
327
|
|
|
322
|
-
function handleSifTrailer(trailer:
|
|
328
|
+
function handleSifTrailer(trailer: NonSharedUint8Array) {
|
|
323
329
|
sifTrailer = trailer;
|
|
324
330
|
participantCryptors.forEach((c) => {
|
|
325
331
|
c.setSifTrailer(trailer);
|
|
@@ -333,10 +339,11 @@ if (self.RTCTransformEvent) {
|
|
|
333
339
|
self.onrtctransform = (event: RTCTransformEvent) => {
|
|
334
340
|
// @ts-ignore
|
|
335
341
|
const transformer = event.transformer;
|
|
336
|
-
const
|
|
337
|
-
|
|
342
|
+
const options = transformer.options as ScriptTransformOptions;
|
|
343
|
+
const { kind, participantIdentity, trackId, codec, hasPacketTrailer } = options;
|
|
338
344
|
messageQueue.run(async () => {
|
|
339
345
|
const cryptor = getTrackCryptor(participantIdentity, trackId);
|
|
346
|
+
cryptor.setHasPacketTrailer(hasPacketTrailer);
|
|
340
347
|
workerLogger.debug('onrtctransform setup', { participantIdentity, trackId, codec });
|
|
341
348
|
cryptor.setupTransform(
|
|
342
349
|
kind,
|
|
@@ -345,6 +352,7 @@ if (self.RTCTransformEvent) {
|
|
|
345
352
|
trackId,
|
|
346
353
|
false,
|
|
347
354
|
codec,
|
|
355
|
+
kind === 'encode' ? options.packetTrailer : undefined,
|
|
348
356
|
);
|
|
349
357
|
});
|
|
350
358
|
};
|
|
@@ -200,7 +200,7 @@ export interface NALUProcessingResult {
|
|
|
200
200
|
* @param naluIndices Indices where NALUs start
|
|
201
201
|
* @returns Detected codec type
|
|
202
202
|
*/
|
|
203
|
-
function detectCodecFromNALUs(data:
|
|
203
|
+
function detectCodecFromNALUs(data: NonSharedUint8Array, naluIndices: number[]): DetectedCodec {
|
|
204
204
|
for (const naluIndex of naluIndices) {
|
|
205
205
|
if (isH264SliceNALU(parseH264NALUType(data[naluIndex]))) return 'h264';
|
|
206
206
|
if (isH265SliceNALU(parseH265NALUType(data[naluIndex]))) return 'h265';
|
|
@@ -216,7 +216,7 @@ function detectCodecFromNALUs(data: Uint8Array, naluIndices: number[]): Detected
|
|
|
216
216
|
* @returns Number of unencrypted bytes (index + 2) or null if no slice found
|
|
217
217
|
*/
|
|
218
218
|
function findSliceNALUUnencryptedBytes(
|
|
219
|
-
data:
|
|
219
|
+
data: NonSharedUint8Array,
|
|
220
220
|
naluIndices: number[],
|
|
221
221
|
codec: 'h264' | 'h265',
|
|
222
222
|
): number | null {
|
|
@@ -246,7 +246,7 @@ function findSliceNALUUnencryptedBytes(
|
|
|
246
246
|
* @param stream Byte stream containing NALUs
|
|
247
247
|
* @returns Array of indices where NALUs start (after the start code)
|
|
248
248
|
*/
|
|
249
|
-
function findNALUIndices(stream:
|
|
249
|
+
function findNALUIndices(stream: NonSharedUint8Array): number[] {
|
|
250
250
|
const result: number[] = [];
|
|
251
251
|
let start = 0,
|
|
252
252
|
pos = 0,
|
|
@@ -309,7 +309,7 @@ function findNALUIndices(stream: Uint8Array): number[] {
|
|
|
309
309
|
* @returns NALU processing result
|
|
310
310
|
*/
|
|
311
311
|
export function processNALUsForEncryption(
|
|
312
|
-
data:
|
|
312
|
+
data: NonSharedUint8Array,
|
|
313
313
|
knownCodec?: 'h264' | 'h265',
|
|
314
314
|
): NALUProcessingResult {
|
|
315
315
|
const naluIndices = findNALUIndices(data);
|
|
@@ -2,29 +2,31 @@ import type { VideoCodec } from '../..';
|
|
|
2
2
|
|
|
3
3
|
// Payload definitions taken from https://github.com/livekit/livekit/blob/master/pkg/sfu/downtrack.go#L104
|
|
4
4
|
|
|
5
|
-
export const VP8KeyFrame8x8:
|
|
5
|
+
export const VP8KeyFrame8x8: NonSharedUint8Array = new Uint8Array([
|
|
6
6
|
0x10, 0x02, 0x00, 0x9d, 0x01, 0x2a, 0x08, 0x00, 0x08, 0x00, 0x00, 0x47, 0x08, 0x85, 0x85, 0x88,
|
|
7
7
|
0x85, 0x84, 0x88, 0x02, 0x02, 0x00, 0x0c, 0x0d, 0x60, 0x00, 0xfe, 0xff, 0xab, 0x50, 0x80,
|
|
8
8
|
]);
|
|
9
9
|
|
|
10
|
-
export const H264KeyFrame2x2SPS:
|
|
10
|
+
export const H264KeyFrame2x2SPS: NonSharedUint8Array = new Uint8Array([
|
|
11
11
|
0x67, 0x42, 0xc0, 0x1f, 0x0f, 0xd9, 0x1f, 0x88, 0x88, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00,
|
|
12
12
|
0x00, 0x03, 0x00, 0xc8, 0x3c, 0x60, 0xc9, 0x20,
|
|
13
13
|
]);
|
|
14
14
|
|
|
15
|
-
export const H264KeyFrame2x2PPS:
|
|
15
|
+
export const H264KeyFrame2x2PPS: NonSharedUint8Array = new Uint8Array([
|
|
16
|
+
0x68, 0x87, 0xcb, 0x83, 0xcb, 0x20,
|
|
17
|
+
]);
|
|
16
18
|
|
|
17
|
-
export const H264KeyFrame2x2IDR:
|
|
19
|
+
export const H264KeyFrame2x2IDR: NonSharedUint8Array = new Uint8Array([
|
|
18
20
|
0x65, 0x88, 0x84, 0x0a, 0xf2, 0x62, 0x80, 0x00, 0xa7, 0xbe,
|
|
19
21
|
]);
|
|
20
22
|
|
|
21
|
-
export const H264KeyFrame2x2:
|
|
23
|
+
export const H264KeyFrame2x2: NonSharedUint8Array[] = [
|
|
22
24
|
H264KeyFrame2x2SPS,
|
|
23
25
|
H264KeyFrame2x2PPS,
|
|
24
26
|
H264KeyFrame2x2IDR,
|
|
25
27
|
];
|
|
26
28
|
|
|
27
|
-
export const OpusSilenceFrame:
|
|
29
|
+
export const OpusSilenceFrame: NonSharedUint8Array = new Uint8Array([
|
|
28
30
|
0xf8, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
29
31
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
30
32
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
@@ -35,7 +37,7 @@ export const OpusSilenceFrame: Uint8Array = new Uint8Array([
|
|
|
35
37
|
/**
|
|
36
38
|
* Create a crypto hash using Web Crypto API for secure comparison operations
|
|
37
39
|
*/
|
|
38
|
-
async function cryptoHash(data:
|
|
40
|
+
async function cryptoHash(data: NonSharedUint8Array | ArrayBuffer): Promise<string> {
|
|
39
41
|
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
40
42
|
const hashArray = new Uint8Array(hashBuffer);
|
|
41
43
|
return Array.from(hashArray)
|
|
@@ -58,7 +60,7 @@ export const CryptoHashes = {
|
|
|
58
60
|
* Check if a byte array matches any of the known SIF payload frame types using secure crypto hashes
|
|
59
61
|
*/
|
|
60
62
|
export async function identifySifPayload(
|
|
61
|
-
data:
|
|
63
|
+
data: NonSharedUint8Array | ArrayBuffer,
|
|
62
64
|
): Promise<VideoCodec | 'opus' | null> {
|
|
63
65
|
const hash = await cryptoHash(data);
|
|
64
66
|
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import Room, { ConnectionState, type RoomEventCallbacks } from './room/Room';
|
|
|
13
13
|
import * as attributes from './room/attribute-typings';
|
|
14
14
|
import LocalDataTrack from './room/data-track/LocalDataTrack';
|
|
15
15
|
import RemoteDataTrack, { type DataTrackSubscribeOptions } from './room/data-track/RemoteDataTrack';
|
|
16
|
+
import { type RemoteDataTrackPipelineOptions } from './room/data-track/types';
|
|
16
17
|
import LocalParticipant from './room/participant/LocalParticipant';
|
|
17
18
|
import Participant, {
|
|
18
19
|
ConnectionQuality,
|
|
@@ -63,6 +64,11 @@ import {
|
|
|
63
64
|
import { getBrowser } from './utils/browserParser';
|
|
64
65
|
|
|
65
66
|
export { RpcError, type RpcInvocationData, type PerformRpcParams } from './room/rpc';
|
|
67
|
+
export type { PacketTrailerMetadata, PacketTrailerPublishOptions } from './packetTrailer/types';
|
|
68
|
+
export {
|
|
69
|
+
PacketTrailerManager,
|
|
70
|
+
type PacketTrailerOptions,
|
|
71
|
+
} from './packetTrailer/PacketTrailerManager';
|
|
66
72
|
|
|
67
73
|
export * from './connectionHelper/ConnectionCheck';
|
|
68
74
|
export * from './connectionHelper/checks/Checker';
|
|
@@ -159,6 +165,7 @@ export type {
|
|
|
159
165
|
ParticipantEventCallbacks,
|
|
160
166
|
PublicationEventCallbacks,
|
|
161
167
|
DataTrackSubscribeOptions,
|
|
168
|
+
RemoteDataTrackPipelineOptions,
|
|
162
169
|
};
|
|
163
170
|
export { DataTrackPacket, type DataTrackPacketHeader } from './room/data-track/packet';
|
|
164
171
|
export {
|
package/src/options.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { E2EEOptions } from './e2ee/types';
|
|
2
|
+
import type { PacketTrailerOptions } from './packetTrailer/PacketTrailerManager';
|
|
2
3
|
import type { ReconnectPolicy } from './room/ReconnectPolicy';
|
|
3
4
|
import type {
|
|
4
5
|
AudioCaptureOptions,
|
|
@@ -100,6 +101,13 @@ export interface InternalRoomOptions {
|
|
|
100
101
|
|
|
101
102
|
loggerName?: string;
|
|
102
103
|
|
|
104
|
+
/**
|
|
105
|
+
* @experimental
|
|
106
|
+
* Options for enabling packet trailers on video tracks.
|
|
107
|
+
* Packet trailers carry frame-level metadata such as user timestamps and frame IDs.
|
|
108
|
+
*/
|
|
109
|
+
packetTrailer?: PacketTrailerOptions;
|
|
110
|
+
|
|
103
111
|
/**
|
|
104
112
|
* will attempt to connect via single peer connection mode.
|
|
105
113
|
* falls back to dual peer connection mode if not available.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { TrackInfo } from '@livekit/protocol';
|
|
3
|
+
import { PacketTrailerManager } from './PacketTrailerManager';
|
|
4
|
+
|
|
5
|
+
describe('PacketTrailerManager', () => {
|
|
6
|
+
const originalRTCRtpSender = window.RTCRtpSender;
|
|
7
|
+
const originalUserAgent = navigator.userAgent;
|
|
8
|
+
const originalRTCRtpScriptTransform = (window as unknown as { RTCRtpScriptTransform?: unknown })
|
|
9
|
+
.RTCRtpScriptTransform;
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
Object.defineProperty(window, 'RTCRtpSender', {
|
|
13
|
+
configurable: true,
|
|
14
|
+
value: originalRTCRtpSender,
|
|
15
|
+
writable: true,
|
|
16
|
+
});
|
|
17
|
+
Object.defineProperty(window.navigator, 'userAgent', {
|
|
18
|
+
configurable: true,
|
|
19
|
+
value: originalUserAgent,
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(window, 'RTCRtpScriptTransform', {
|
|
22
|
+
configurable: true,
|
|
23
|
+
value: originalRTCRtpScriptTransform,
|
|
24
|
+
writable: true,
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(globalThis, 'RTCRtpScriptTransform', {
|
|
27
|
+
configurable: true,
|
|
28
|
+
value: originalRTCRtpScriptTransform,
|
|
29
|
+
writable: true,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function stubInsertableStreamsSupport() {
|
|
34
|
+
class MockRTCRtpSender {
|
|
35
|
+
createEncodedStreams() {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Object.defineProperty(window, 'RTCRtpSender', {
|
|
39
|
+
configurable: true,
|
|
40
|
+
value: MockRTCRtpSender,
|
|
41
|
+
writable: true,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function useSafariUserAgent() {
|
|
46
|
+
Object.defineProperty(window.navigator, 'userAgent', {
|
|
47
|
+
configurable: true,
|
|
48
|
+
value:
|
|
49
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function setScriptTransform(mock: unknown) {
|
|
54
|
+
Object.defineProperty(window, 'RTCRtpScriptTransform', {
|
|
55
|
+
configurable: true,
|
|
56
|
+
value: mock,
|
|
57
|
+
writable: true,
|
|
58
|
+
});
|
|
59
|
+
Object.defineProperty(globalThis, 'RTCRtpScriptTransform', {
|
|
60
|
+
configurable: true,
|
|
61
|
+
value: mock,
|
|
62
|
+
writable: true,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function setupWorkerReceiver(manager: PacketTrailerManager, receiver: RTCRtpReceiver) {
|
|
67
|
+
(
|
|
68
|
+
manager as unknown as {
|
|
69
|
+
setupWorkerReceiver: (receiver: RTCRtpReceiver, newTrackId: string) => void;
|
|
70
|
+
}
|
|
71
|
+
).setupWorkerReceiver(receiver, 'track-id');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function setupReceiver(
|
|
75
|
+
manager: PacketTrailerManager,
|
|
76
|
+
receiver: RTCRtpReceiver,
|
|
77
|
+
trackId: string,
|
|
78
|
+
trackInfo?: TrackInfo,
|
|
79
|
+
) {
|
|
80
|
+
(
|
|
81
|
+
manager as unknown as {
|
|
82
|
+
setupReceiver: (
|
|
83
|
+
track: { receiver: RTCRtpReceiver; mediaStreamID: string },
|
|
84
|
+
trackInfo?: TrackInfo,
|
|
85
|
+
) => void;
|
|
86
|
+
}
|
|
87
|
+
).setupReceiver({ receiver, mediaStreamID: trackId }, trackInfo);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function makeReceiver() {
|
|
91
|
+
const readable = {} as ReadableStream;
|
|
92
|
+
const writable = {} as WritableStream;
|
|
93
|
+
const createEncodedStreams = vi.fn(() => ({ readable, writable }));
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
receiver: { createEncodedStreams } as unknown as RTCRtpReceiver,
|
|
97
|
+
readable,
|
|
98
|
+
writable,
|
|
99
|
+
createEncodedStreams,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
it('uses RTCRtpScriptTransform for packet trailer extraction when supported', () => {
|
|
104
|
+
useSafariUserAgent();
|
|
105
|
+
const transform = {};
|
|
106
|
+
const RTCRtpScriptTransform = vi.fn(() => transform);
|
|
107
|
+
setScriptTransform(RTCRtpScriptTransform);
|
|
108
|
+
|
|
109
|
+
const worker = {} as Worker;
|
|
110
|
+
const manager = new PacketTrailerManager({ worker });
|
|
111
|
+
const receiver = {
|
|
112
|
+
createEncodedStreams: vi.fn(),
|
|
113
|
+
} as unknown as RTCRtpReceiver;
|
|
114
|
+
|
|
115
|
+
setupWorkerReceiver(manager, receiver);
|
|
116
|
+
|
|
117
|
+
expect(RTCRtpScriptTransform).toHaveBeenCalledWith(worker, {
|
|
118
|
+
kind: 'decode',
|
|
119
|
+
trackId: 'track-id',
|
|
120
|
+
});
|
|
121
|
+
expect((receiver as unknown as { transform: unknown }).transform).toBe(transform);
|
|
122
|
+
expect(
|
|
123
|
+
(receiver as unknown as { createEncodedStreams: ReturnType<typeof vi.fn> })
|
|
124
|
+
.createEncodedStreams,
|
|
125
|
+
).not.toHaveBeenCalled();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('sets up a passthrough receiver pipeline when a subscribed track has no packet trailer features', () => {
|
|
129
|
+
stubInsertableStreamsSupport();
|
|
130
|
+
|
|
131
|
+
const worker = { postMessage: vi.fn() } as unknown as Worker;
|
|
132
|
+
const manager = new PacketTrailerManager({ worker });
|
|
133
|
+
const { receiver, readable, writable, createEncodedStreams } = makeReceiver();
|
|
134
|
+
|
|
135
|
+
setupReceiver(manager, receiver, 'track-without-trailer');
|
|
136
|
+
|
|
137
|
+
expect(createEncodedStreams).toHaveBeenCalledTimes(1);
|
|
138
|
+
expect(worker.postMessage).toHaveBeenCalledWith(
|
|
139
|
+
{
|
|
140
|
+
kind: 'decode',
|
|
141
|
+
data: {
|
|
142
|
+
readableStream: readable,
|
|
143
|
+
writableStream: writable,
|
|
144
|
+
trackId: 'track-without-trailer',
|
|
145
|
+
hasPacketTrailer: false,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
[readable, writable],
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('updates a reused receiver from trailer extraction to passthrough for tracks without packet trailer features', () => {
|
|
153
|
+
stubInsertableStreamsSupport();
|
|
154
|
+
|
|
155
|
+
const worker = { postMessage: vi.fn() } as unknown as Worker;
|
|
156
|
+
const manager = new PacketTrailerManager({ worker });
|
|
157
|
+
const { receiver } = makeReceiver();
|
|
158
|
+
const trackInfo = { packetTrailerFeatures: [1] } as unknown as TrackInfo;
|
|
159
|
+
|
|
160
|
+
setupReceiver(manager, receiver, 'track-with-trailer', trackInfo);
|
|
161
|
+
setupReceiver(manager, receiver, 'track-without-trailer');
|
|
162
|
+
|
|
163
|
+
expect(worker.postMessage).toHaveBeenLastCalledWith({
|
|
164
|
+
kind: 'updateTrackId',
|
|
165
|
+
data: {
|
|
166
|
+
oldTrackId: 'track-with-trailer',
|
|
167
|
+
newTrackId: 'track-without-trailer',
|
|
168
|
+
hasPacketTrailer: false,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|