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
package/src/e2ee/E2eeManager.ts
CHANGED
|
@@ -2,16 +2,24 @@ import { Encryption_Type, TrackInfo } from '@livekit/protocol';
|
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
import type TypedEventEmitter from 'typed-emitter';
|
|
4
4
|
import log, { LogLevel, workerLogger } from '../logger';
|
|
5
|
+
import { hasPacketTrailerPublishOptions } from '../packetTrailer/utils';
|
|
5
6
|
import type RTCEngine from '../room/RTCEngine';
|
|
6
7
|
import type Room from '../room/Room';
|
|
7
8
|
import { ConnectionState } from '../room/Room';
|
|
8
9
|
import { DeviceUnsupportedError } from '../room/errors';
|
|
9
10
|
import { EngineEvent, ParticipantEvent, RoomEvent } from '../room/events';
|
|
10
11
|
import type RemoteTrack from '../room/track/RemoteTrack';
|
|
12
|
+
import RemoteVideoTrack from '../room/track/RemoteVideoTrack';
|
|
11
13
|
import type { Track } from '../room/track/Track';
|
|
12
|
-
import type { VideoCodec } from '../room/track/options';
|
|
14
|
+
import type { TrackPublishOptions, VideoCodec } from '../room/track/options';
|
|
13
15
|
import { mimeTypeToVideoCodecString } from '../room/track/utils';
|
|
14
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
Future,
|
|
18
|
+
isLocalTrack,
|
|
19
|
+
isSafariBased,
|
|
20
|
+
isScriptTransformSupportedForWorker,
|
|
21
|
+
isVideoTrack,
|
|
22
|
+
} from '../room/utils';
|
|
15
23
|
import type { BaseKeyProvider } from './KeyProvider';
|
|
16
24
|
import { E2EE_FLAG } from './constants';
|
|
17
25
|
import { type E2EEManagerCallbacks, EncryptionEvent, KeyProviderEvent } from './events';
|
|
@@ -34,7 +42,7 @@ import type {
|
|
|
34
42
|
SifTrailerMessage,
|
|
35
43
|
UpdateCodecMessage,
|
|
36
44
|
} from './types';
|
|
37
|
-
import { isE2EESupported
|
|
45
|
+
import { isE2EESupported } from './utils';
|
|
38
46
|
|
|
39
47
|
export interface BaseE2EEManager {
|
|
40
48
|
setup(room: Room): void;
|
|
@@ -42,11 +50,11 @@ export interface BaseE2EEManager {
|
|
|
42
50
|
isEnabled: boolean;
|
|
43
51
|
isDataChannelEncryptionEnabled: boolean;
|
|
44
52
|
setParticipantCryptorEnabled(enabled: boolean, participantIdentity: string): void;
|
|
45
|
-
setSifTrailer(trailer:
|
|
46
|
-
encryptData(data:
|
|
53
|
+
setSifTrailer(trailer: NonSharedUint8Array): void;
|
|
54
|
+
encryptData(data: NonSharedUint8Array): Promise<EncryptDataResponseMessage['data']>;
|
|
47
55
|
handleEncryptedData(
|
|
48
|
-
payload:
|
|
49
|
-
iv:
|
|
56
|
+
payload: NonSharedUint8Array,
|
|
57
|
+
iv: NonSharedUint8Array,
|
|
50
58
|
participantIdentity: string,
|
|
51
59
|
keyIndex: number,
|
|
52
60
|
): Promise<DecryptDataResponseMessage['data']>;
|
|
@@ -133,7 +141,7 @@ export class E2EEManager
|
|
|
133
141
|
/**
|
|
134
142
|
* @internal
|
|
135
143
|
*/
|
|
136
|
-
setSifTrailer(trailer:
|
|
144
|
+
setSifTrailer(trailer: NonSharedUint8Array) {
|
|
137
145
|
if (!trailer || trailer.length === 0) {
|
|
138
146
|
log.warn("ignoring server sent trailer as it's empty");
|
|
139
147
|
} else {
|
|
@@ -221,6 +229,9 @@ export class E2EEManager
|
|
|
221
229
|
encryptFuture.resolve(data as EncryptDataResponseMessage['data']);
|
|
222
230
|
}
|
|
223
231
|
break;
|
|
232
|
+
case 'packetTrailerMetadata':
|
|
233
|
+
this.handlePacketTrailerMetadata(data.trackId, data.rtpTimestamp, data.ssrc, data.metadata);
|
|
234
|
+
break;
|
|
224
235
|
default:
|
|
225
236
|
break;
|
|
226
237
|
}
|
|
@@ -231,6 +242,33 @@ export class E2EEManager
|
|
|
231
242
|
this.emit(EncryptionEvent.EncryptionError, ev.error, undefined);
|
|
232
243
|
};
|
|
233
244
|
|
|
245
|
+
private handlePacketTrailerMetadata(
|
|
246
|
+
trackId: string,
|
|
247
|
+
rtpTimestamp: number,
|
|
248
|
+
ssrc: number,
|
|
249
|
+
metadata: { userTimestamp: bigint; frameId: number },
|
|
250
|
+
) {
|
|
251
|
+
if (!this.room) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
for (const participant of [
|
|
255
|
+
this.room.localParticipant,
|
|
256
|
+
...this.room.remoteParticipants.values(),
|
|
257
|
+
]) {
|
|
258
|
+
for (const pub of participant.trackPublications.values()) {
|
|
259
|
+
if (
|
|
260
|
+
pub.track &&
|
|
261
|
+
pub.track.mediaStreamID === trackId &&
|
|
262
|
+
pub.track instanceof RemoteVideoTrack &&
|
|
263
|
+
pub.track.packetTrailerExtractor
|
|
264
|
+
) {
|
|
265
|
+
pub.track.packetTrailerExtractor.storeMetadata(rtpTimestamp, ssrc, metadata);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
234
272
|
public setupEngine(engine: RTCEngine) {
|
|
235
273
|
engine.on(EngineEvent.RTPVideoMapUpdate, (rtpMap) => {
|
|
236
274
|
this.postRTPMap(rtpMap);
|
|
@@ -299,6 +337,7 @@ export class E2EEManager
|
|
|
299
337
|
trackId: publication.track!.mediaStreamID,
|
|
300
338
|
codec: mimeTypeToVideoCodecString(publication.trackInfo!.codecs[0].mimeType),
|
|
301
339
|
participantIdentity: this.room!.localParticipant.identity,
|
|
340
|
+
hasPacketTrailer: false,
|
|
302
341
|
},
|
|
303
342
|
};
|
|
304
343
|
|
|
@@ -314,7 +353,7 @@ export class E2EEManager
|
|
|
314
353
|
);
|
|
315
354
|
}
|
|
316
355
|
|
|
317
|
-
async encryptData(data:
|
|
356
|
+
async encryptData(data: NonSharedUint8Array): Promise<EncryptDataResponseMessage['data']> {
|
|
318
357
|
if (!this.worker) {
|
|
319
358
|
throw Error('could not encrypt data, worker is missing');
|
|
320
359
|
}
|
|
@@ -337,8 +376,8 @@ export class E2EEManager
|
|
|
337
376
|
}
|
|
338
377
|
|
|
339
378
|
handleEncryptedData(
|
|
340
|
-
payload:
|
|
341
|
-
iv:
|
|
379
|
+
payload: NonSharedUint8Array,
|
|
380
|
+
iv: NonSharedUint8Array,
|
|
342
381
|
participantIdentity: string,
|
|
343
382
|
keyIndex: number,
|
|
344
383
|
) {
|
|
@@ -428,7 +467,7 @@ export class E2EEManager
|
|
|
428
467
|
this.worker.postMessage(msg);
|
|
429
468
|
}
|
|
430
469
|
|
|
431
|
-
private postSifTrailer(trailer:
|
|
470
|
+
private postSifTrailer(trailer: NonSharedUint8Array) {
|
|
432
471
|
if (!this.worker) {
|
|
433
472
|
throw Error('could not post SIF trailer, worker is missing');
|
|
434
473
|
}
|
|
@@ -448,11 +487,16 @@ export class E2EEManager
|
|
|
448
487
|
if (!trackInfo?.mimeType || trackInfo.mimeType === '') {
|
|
449
488
|
throw new TypeError('MimeType missing from trackInfo, cannot set up E2EE cryptor');
|
|
450
489
|
}
|
|
490
|
+
const hasPacketTrailer =
|
|
491
|
+
track.kind === 'video' &&
|
|
492
|
+
!!trackInfo.packetTrailerFeatures &&
|
|
493
|
+
trackInfo.packetTrailerFeatures.length > 0;
|
|
451
494
|
this.handleReceiver(
|
|
452
495
|
track.receiver,
|
|
453
496
|
track.mediaStreamID,
|
|
454
497
|
remoteId,
|
|
455
498
|
track.kind === 'video' ? mimeTypeToVideoCodecString(trackInfo.mimeType) : undefined,
|
|
499
|
+
hasPacketTrailer,
|
|
456
500
|
);
|
|
457
501
|
}
|
|
458
502
|
|
|
@@ -461,7 +505,12 @@ export class E2EEManager
|
|
|
461
505
|
if (!sender) log.warn('early return because sender is not ready');
|
|
462
506
|
return;
|
|
463
507
|
}
|
|
464
|
-
this.handleSender(
|
|
508
|
+
this.handleSender(
|
|
509
|
+
sender,
|
|
510
|
+
track.mediaStreamID,
|
|
511
|
+
undefined,
|
|
512
|
+
isVideoTrack(track) ? track.publishOptions?.packetTrailer : undefined,
|
|
513
|
+
);
|
|
465
514
|
}
|
|
466
515
|
|
|
467
516
|
/**
|
|
@@ -473,35 +522,33 @@ export class E2EEManager
|
|
|
473
522
|
receiver: RTCRtpReceiver,
|
|
474
523
|
trackId: string,
|
|
475
524
|
participantIdentity: string,
|
|
476
|
-
codec
|
|
525
|
+
codec: VideoCodec | undefined,
|
|
526
|
+
hasPacketTrailer: boolean,
|
|
477
527
|
) {
|
|
478
528
|
if (!this.worker) {
|
|
479
529
|
return;
|
|
480
530
|
}
|
|
481
531
|
|
|
482
|
-
if (
|
|
483
|
-
isScriptTransformSupported() &&
|
|
484
|
-
// Chrome occasionally throws an `InvalidState` error when using script transforms directly after introducing this API in 141.
|
|
485
|
-
// Disabling it for Chrome based browsers until the API has stabilized
|
|
486
|
-
!isChromiumBased()
|
|
487
|
-
) {
|
|
532
|
+
if (isScriptTransformSupportedForWorker()) {
|
|
488
533
|
const options: ScriptTransformOptions = {
|
|
489
534
|
kind: 'decode',
|
|
490
535
|
participantIdentity,
|
|
491
536
|
trackId,
|
|
492
537
|
codec,
|
|
538
|
+
hasPacketTrailer,
|
|
493
539
|
};
|
|
494
540
|
// @ts-ignore
|
|
495
541
|
receiver.transform = new RTCRtpScriptTransform(this.worker, options);
|
|
496
542
|
} else {
|
|
497
543
|
if (E2EE_FLAG in receiver && codec) {
|
|
498
|
-
//
|
|
544
|
+
// update track-specific state when the transceiver is reused
|
|
499
545
|
const msg: UpdateCodecMessage = {
|
|
500
546
|
kind: 'updateCodec',
|
|
501
547
|
data: {
|
|
502
548
|
trackId,
|
|
503
549
|
codec,
|
|
504
|
-
participantIdentity
|
|
550
|
+
participantIdentity,
|
|
551
|
+
hasPacketTrailer,
|
|
505
552
|
},
|
|
506
553
|
};
|
|
507
554
|
this.worker.postMessage(msg);
|
|
@@ -532,6 +579,7 @@ export class E2EEManager
|
|
|
532
579
|
codec,
|
|
533
580
|
participantIdentity: participantIdentity,
|
|
534
581
|
isReuse: E2EE_FLAG in receiver,
|
|
582
|
+
hasPacketTrailer,
|
|
535
583
|
},
|
|
536
584
|
};
|
|
537
585
|
this.worker.postMessage(msg, [readable, writable]);
|
|
@@ -546,7 +594,12 @@ export class E2EEManager
|
|
|
546
594
|
* a frame encoder.
|
|
547
595
|
*
|
|
548
596
|
*/
|
|
549
|
-
private handleSender(
|
|
597
|
+
private handleSender(
|
|
598
|
+
sender: RTCRtpSender,
|
|
599
|
+
trackId: string,
|
|
600
|
+
codec?: VideoCodec,
|
|
601
|
+
packetTrailer?: TrackPublishOptions['packetTrailer'],
|
|
602
|
+
) {
|
|
550
603
|
if (E2EE_FLAG in sender || !this.worker) {
|
|
551
604
|
return;
|
|
552
605
|
}
|
|
@@ -555,18 +608,15 @@ export class E2EEManager
|
|
|
555
608
|
throw TypeError('local identity needs to be known in order to set up encrypted sender');
|
|
556
609
|
}
|
|
557
610
|
|
|
558
|
-
if (
|
|
559
|
-
isScriptTransformSupported() &&
|
|
560
|
-
// Chrome occasionally throws an `InvalidState` error when using script transforms directly after introducing this API in 141.
|
|
561
|
-
// Disabling it for Chrome based browsers until the API has stabilized
|
|
562
|
-
!isChromiumBased()
|
|
563
|
-
) {
|
|
611
|
+
if (isScriptTransformSupportedForWorker()) {
|
|
564
612
|
log.info('initialize script transform');
|
|
565
|
-
const options = {
|
|
613
|
+
const options: ScriptTransformOptions = {
|
|
566
614
|
kind: 'encode',
|
|
567
615
|
participantIdentity: this.room.localParticipant.identity,
|
|
568
616
|
trackId,
|
|
569
617
|
codec,
|
|
618
|
+
hasPacketTrailer: hasPacketTrailerPublishOptions(packetTrailer),
|
|
619
|
+
packetTrailer,
|
|
570
620
|
};
|
|
571
621
|
// @ts-ignore
|
|
572
622
|
sender.transform = new RTCRtpScriptTransform(this.worker, options);
|
|
@@ -583,6 +633,8 @@ export class E2EEManager
|
|
|
583
633
|
trackId,
|
|
584
634
|
participantIdentity: this.room.localParticipant.identity,
|
|
585
635
|
isReuse: false,
|
|
636
|
+
hasPacketTrailer: hasPacketTrailerPublishOptions(packetTrailer),
|
|
637
|
+
packetTrailer,
|
|
586
638
|
},
|
|
587
639
|
};
|
|
588
640
|
this.worker.postMessage(msg, [senderStreams.readable, senderStreams.writable]);
|
package/src/e2ee/types.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { LogLevel } from '../logger';
|
|
2
|
+
import type { PacketTrailerFramePayload } from '../packetTrailer/packetTrailer';
|
|
3
|
+
import type { PacketTrailerPublishOptions } from '../packetTrailer/types';
|
|
2
4
|
import type { VideoCodec } from '../room/track/options';
|
|
3
5
|
import type { BaseE2EEManager } from './E2eeManager';
|
|
4
6
|
import type { BaseKeyProvider } from './KeyProvider';
|
|
@@ -38,7 +40,7 @@ export interface RTPVideoMapMessage extends BaseMessage {
|
|
|
38
40
|
export interface SifTrailerMessage extends BaseMessage {
|
|
39
41
|
kind: 'setSifTrailer';
|
|
40
42
|
data: {
|
|
41
|
-
trailer:
|
|
43
|
+
trailer: NonSharedUint8Array;
|
|
42
44
|
};
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -51,6 +53,16 @@ export interface EncodeMessage extends BaseMessage {
|
|
|
51
53
|
trackId: string;
|
|
52
54
|
codec?: VideoCodec;
|
|
53
55
|
isReuse: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Whether the published track advertises packet trailer features.
|
|
58
|
+
* When false, the cryptor skips the per-frame trailer extraction path
|
|
59
|
+
* entirely on decode.
|
|
60
|
+
*/
|
|
61
|
+
hasPacketTrailer: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Packet trailer metadata to append on published video frames.
|
|
64
|
+
*/
|
|
65
|
+
packetTrailer?: PacketTrailerPublishOptions;
|
|
54
66
|
};
|
|
55
67
|
}
|
|
56
68
|
|
|
@@ -68,6 +80,7 @@ export interface UpdateCodecMessage extends BaseMessage {
|
|
|
68
80
|
participantIdentity: string;
|
|
69
81
|
trackId: string;
|
|
70
82
|
codec: VideoCodec;
|
|
83
|
+
hasPacketTrailer: boolean;
|
|
71
84
|
};
|
|
72
85
|
}
|
|
73
86
|
|
|
@@ -116,8 +129,8 @@ export interface DecryptDataRequestMessage extends BaseMessage {
|
|
|
116
129
|
kind: 'decryptDataRequest';
|
|
117
130
|
data: {
|
|
118
131
|
uuid: string;
|
|
119
|
-
payload:
|
|
120
|
-
iv:
|
|
132
|
+
payload: NonSharedUint8Array;
|
|
133
|
+
iv: NonSharedUint8Array;
|
|
121
134
|
participantIdentity: string;
|
|
122
135
|
keyIndex: number;
|
|
123
136
|
};
|
|
@@ -127,7 +140,7 @@ export interface DecryptDataResponseMessage extends BaseMessage {
|
|
|
127
140
|
kind: 'decryptDataResponse';
|
|
128
141
|
data: {
|
|
129
142
|
uuid: string;
|
|
130
|
-
payload:
|
|
143
|
+
payload: NonSharedUint8Array;
|
|
131
144
|
};
|
|
132
145
|
}
|
|
133
146
|
|
|
@@ -135,7 +148,7 @@ export interface EncryptDataRequestMessage extends BaseMessage {
|
|
|
135
148
|
kind: 'encryptDataRequest';
|
|
136
149
|
data: {
|
|
137
150
|
uuid: string;
|
|
138
|
-
payload:
|
|
151
|
+
payload: NonSharedUint8Array;
|
|
139
152
|
participantIdentity: string;
|
|
140
153
|
};
|
|
141
154
|
}
|
|
@@ -144,12 +157,17 @@ export interface EncryptDataResponseMessage extends BaseMessage {
|
|
|
144
157
|
kind: 'encryptDataResponse';
|
|
145
158
|
data: {
|
|
146
159
|
uuid: string;
|
|
147
|
-
payload:
|
|
148
|
-
iv:
|
|
160
|
+
payload: NonSharedUint8Array;
|
|
161
|
+
iv: NonSharedUint8Array;
|
|
149
162
|
keyIndex: number;
|
|
150
163
|
};
|
|
151
164
|
}
|
|
152
165
|
|
|
166
|
+
export interface PTMetadataFromE2EEMessage extends BaseMessage {
|
|
167
|
+
kind: 'packetTrailerMetadata';
|
|
168
|
+
data: PacketTrailerFramePayload;
|
|
169
|
+
}
|
|
170
|
+
|
|
153
171
|
export type E2EEWorkerMessage =
|
|
154
172
|
| InitMessage
|
|
155
173
|
| SetKeyMessage
|
|
@@ -166,7 +184,8 @@ export type E2EEWorkerMessage =
|
|
|
166
184
|
| DecryptDataRequestMessage
|
|
167
185
|
| DecryptDataResponseMessage
|
|
168
186
|
| EncryptDataRequestMessage
|
|
169
|
-
| EncryptDataResponseMessage
|
|
187
|
+
| EncryptDataResponseMessage
|
|
188
|
+
| PTMetadataFromE2EEMessage;
|
|
170
189
|
|
|
171
190
|
export type KeySet = { material: CryptoKey; encryptionKey: CryptoKey };
|
|
172
191
|
|
|
@@ -221,4 +240,14 @@ export type ScriptTransformOptions = {
|
|
|
221
240
|
participantIdentity: string;
|
|
222
241
|
trackId: string;
|
|
223
242
|
codec?: VideoCodec;
|
|
243
|
+
/**
|
|
244
|
+
* Whether the published track advertises packet trailer features.
|
|
245
|
+
* When false, the cryptor skips the per-frame trailer extraction path
|
|
246
|
+
* entirely on decode.
|
|
247
|
+
*/
|
|
248
|
+
hasPacketTrailer: boolean;
|
|
249
|
+
/**
|
|
250
|
+
* Packet trailer metadata to append on published video frames.
|
|
251
|
+
*/
|
|
252
|
+
packetTrailer?: PacketTrailerPublishOptions;
|
|
224
253
|
};
|
package/src/e2ee/utils.ts
CHANGED
|
@@ -8,11 +8,12 @@ export function isE2EESupported() {
|
|
|
8
8
|
|
|
9
9
|
export function isScriptTransformSupported() {
|
|
10
10
|
// @ts-ignore
|
|
11
|
-
return typeof window.RTCRtpScriptTransform !== 'undefined';
|
|
11
|
+
return typeof window !== 'undefined' && typeof window.RTCRtpScriptTransform !== 'undefined';
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function isInsertableStreamSupported() {
|
|
15
15
|
return (
|
|
16
|
+
typeof window !== 'undefined' &&
|
|
16
17
|
typeof window.RTCRtpSender !== 'undefined' &&
|
|
17
18
|
// @ts-ignore
|
|
18
19
|
typeof window.RTCRtpSender.prototype.createEncodedStreams !== 'undefined'
|
|
@@ -26,7 +27,7 @@ export function isVideoFrame(
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export async function importKey(
|
|
29
|
-
keyBytes:
|
|
30
|
+
keyBytes: NonSharedUint8Array | ArrayBuffer,
|
|
30
31
|
algorithm: string | { name: string } = { name: ENCRYPTION_ALGORITHM },
|
|
31
32
|
usage: 'derive' | 'encrypt' = 'encrypt',
|
|
32
33
|
) {
|
|
@@ -112,7 +113,7 @@ export async function deriveKeys(material: CryptoKey, options: KeyProviderOption
|
|
|
112
113
|
return { material, encryptionKey };
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
export function createE2EEKey():
|
|
116
|
+
export function createE2EEKey(): NonSharedUint8Array {
|
|
116
117
|
return window.crypto.getRandomValues(new Uint8Array(32));
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -127,14 +128,14 @@ export async function ratchet(material: CryptoKey, salt: string): Promise<ArrayB
|
|
|
127
128
|
return crypto.subtle.deriveBits(algorithmOptions, material, 256);
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
export function needsRbspUnescaping(frameData:
|
|
131
|
+
export function needsRbspUnescaping(frameData: NonSharedUint8Array) {
|
|
131
132
|
for (var i = 0; i < frameData.length - 3; i++) {
|
|
132
133
|
if (frameData[i] == 0 && frameData[i + 1] == 0 && frameData[i + 2] == 3) return true;
|
|
133
134
|
}
|
|
134
135
|
return false;
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
export function parseRbsp(stream:
|
|
138
|
+
export function parseRbsp(stream: NonSharedUint8Array): NonSharedUint8Array {
|
|
138
139
|
const dataOut: number[] = [];
|
|
139
140
|
var length = stream.length;
|
|
140
141
|
for (var i = 0; i < stream.length; ) {
|
|
@@ -159,7 +160,7 @@ export function parseRbsp(stream: Uint8Array): Uint8Array {
|
|
|
159
160
|
const kZerosInStartSequence = 2;
|
|
160
161
|
const kEmulationByte = 3;
|
|
161
162
|
|
|
162
|
-
export function writeRbsp(data_in:
|
|
163
|
+
export function writeRbsp(data_in: NonSharedUint8Array): NonSharedUint8Array {
|
|
163
164
|
const dataOut: number[] = [];
|
|
164
165
|
var numConsecutiveZeros = 0;
|
|
165
166
|
for (var i = 0; i < data_in.length; ++i) {
|
|
@@ -21,11 +21,11 @@ export class DataCryptor {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
static async encrypt(
|
|
24
|
-
data:
|
|
24
|
+
data: NonSharedUint8Array,
|
|
25
25
|
keys: ParticipantKeyHandler,
|
|
26
26
|
): Promise<{
|
|
27
|
-
payload:
|
|
28
|
-
iv:
|
|
27
|
+
payload: NonSharedUint8Array;
|
|
28
|
+
iv: NonSharedUint8Array;
|
|
29
29
|
keyIndex: number;
|
|
30
30
|
}> {
|
|
31
31
|
const iv = DataCryptor.makeIV(performance.now());
|
|
@@ -51,14 +51,14 @@ export class DataCryptor {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
static async decrypt(
|
|
54
|
-
data:
|
|
55
|
-
iv:
|
|
54
|
+
data: NonSharedUint8Array,
|
|
55
|
+
iv: NonSharedUint8Array,
|
|
56
56
|
keys: ParticipantKeyHandler,
|
|
57
57
|
keyIndex: number = 0,
|
|
58
58
|
initialMaterial?: KeySet,
|
|
59
59
|
ratchetOpts: DecodeRatchetOptions = { ratchetCount: 0 },
|
|
60
60
|
): Promise<{
|
|
61
|
-
payload:
|
|
61
|
+
payload: NonSharedUint8Array;
|
|
62
62
|
}> {
|
|
63
63
|
const keySet = await keys.getKeySet(keyIndex);
|
|
64
64
|
if (!keySet) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vitest } from 'vitest';
|
|
2
|
+
import { appendPacketTrailer, extractPacketTrailer } from '../../packetTrailer/packetTrailer';
|
|
3
|
+
import type { PacketTrailerPublishOptions } from '../../packetTrailer/types';
|
|
2
4
|
import { IV_LENGTH, KEY_PROVIDER_DEFAULTS } from '../constants';
|
|
3
5
|
import { CryptorEvent } from '../events';
|
|
4
6
|
import type { KeyProviderOptions } from '../types';
|
|
@@ -13,7 +15,7 @@ function mockEncryptedRTCEncodedVideoFrame(keyIndex: number): RTCEncodedVideoFra
|
|
|
13
15
|
return mockRTCEncodedVideoFrame(data);
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
function mockRTCEncodedVideoFrame(data:
|
|
18
|
+
function mockRTCEncodedVideoFrame(data: NonSharedUint8Array): RTCEncodedVideoFrame {
|
|
17
19
|
return {
|
|
18
20
|
data: data.buffer,
|
|
19
21
|
timestamp: vitest.getMockedSystemTime()?.getTime() ?? 0,
|
|
@@ -24,7 +26,7 @@ function mockRTCEncodedVideoFrame(data: Uint8Array): RTCEncodedVideoFrame {
|
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
function mockFrameTrailer(keyIndex: number):
|
|
29
|
+
function mockFrameTrailer(keyIndex: number): NonSharedUint8Array {
|
|
28
30
|
const frameTrailer = new Uint8Array(2);
|
|
29
31
|
|
|
30
32
|
frameTrailer[0] = IV_LENGTH;
|
|
@@ -67,14 +69,21 @@ function prepareParticipantTestDecoder(
|
|
|
67
69
|
function prepareParticipantTestEncoder(
|
|
68
70
|
participantIdentity: string,
|
|
69
71
|
partialKeyProviderOptions: Partial<KeyProviderOptions>,
|
|
72
|
+
packetTrailer?: PacketTrailerPublishOptions,
|
|
70
73
|
) {
|
|
71
|
-
return prepareParticipantTest(
|
|
74
|
+
return prepareParticipantTest(
|
|
75
|
+
'encode',
|
|
76
|
+
participantIdentity,
|
|
77
|
+
partialKeyProviderOptions,
|
|
78
|
+
packetTrailer,
|
|
79
|
+
);
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
function prepareParticipantTest(
|
|
75
83
|
mode: 'encode' | 'decode',
|
|
76
84
|
participantIdentity: string,
|
|
77
85
|
partialKeyProviderOptions: Partial<KeyProviderOptions>,
|
|
86
|
+
packetTrailer?: PacketTrailerPublishOptions,
|
|
78
87
|
): {
|
|
79
88
|
keys: ParticipantKeyHandler;
|
|
80
89
|
cryptor: FrameCryptor;
|
|
@@ -95,7 +104,15 @@ function prepareParticipantTest(
|
|
|
95
104
|
|
|
96
105
|
const input = new TestUnderlyingSource<RTCEncodedVideoFrame>();
|
|
97
106
|
const output = new TestUnderlyingSink<RTCEncodedVideoFrame>();
|
|
98
|
-
cryptor.setupTransform(
|
|
107
|
+
cryptor.setupTransform(
|
|
108
|
+
mode,
|
|
109
|
+
new ReadableStream(input),
|
|
110
|
+
new WritableStream(output),
|
|
111
|
+
'testTrack',
|
|
112
|
+
false,
|
|
113
|
+
undefined,
|
|
114
|
+
packetTrailer,
|
|
115
|
+
);
|
|
99
116
|
|
|
100
117
|
return { keys, cryptor, input, output };
|
|
101
118
|
}
|
|
@@ -215,6 +232,127 @@ describe('FrameCryptor', () => {
|
|
|
215
232
|
vitest.useRealTimers();
|
|
216
233
|
}
|
|
217
234
|
});
|
|
235
|
+
|
|
236
|
+
it('appends packet trailer after encryption', async () => {
|
|
237
|
+
vitest.useFakeTimers();
|
|
238
|
+
const now = new Date('2025-04-10T12:00:00.123Z');
|
|
239
|
+
vitest.setSystemTime(now);
|
|
240
|
+
try {
|
|
241
|
+
const { keys, input, output } = prepareParticipantTestEncoder(
|
|
242
|
+
participantIdentity,
|
|
243
|
+
{},
|
|
244
|
+
{ timestamp: true, frameId: true },
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
await keys.setKey(await createKeyMaterialFromString('key1'), 1);
|
|
248
|
+
|
|
249
|
+
const frame = mockRTCEncodedVideoFrame(
|
|
250
|
+
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
input.write(frame);
|
|
254
|
+
await vitest.waitFor(() => expect(output.chunks).toHaveLength(1));
|
|
255
|
+
|
|
256
|
+
const extracted = extractPacketTrailer(frame.data);
|
|
257
|
+
expect(extracted.metadata?.userTimestamp).toBeGreaterThanOrEqual(
|
|
258
|
+
BigInt(now.getTime()) * BigInt(1000),
|
|
259
|
+
);
|
|
260
|
+
expect(extracted.metadata?.frameId).toBe(1);
|
|
261
|
+
|
|
262
|
+
const frameTrailer = new Uint8Array(
|
|
263
|
+
extracted.data.buffer.slice(extracted.data.byteLength - 2),
|
|
264
|
+
);
|
|
265
|
+
expect(frameTrailer[0]).toEqual(IV_LENGTH);
|
|
266
|
+
expect(frameTrailer[1]).toEqual(1);
|
|
267
|
+
} finally {
|
|
268
|
+
vitest.useRealTimers();
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('does not append packet trailer after encryption when no trailer features are enabled', async () => {
|
|
273
|
+
vitest.useFakeTimers();
|
|
274
|
+
try {
|
|
275
|
+
const { keys, input, output } = prepareParticipantTestEncoder(
|
|
276
|
+
participantIdentity,
|
|
277
|
+
{},
|
|
278
|
+
{ timestamp: false, frameId: false },
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
await keys.setKey(await createKeyMaterialFromString('key1'), 1);
|
|
282
|
+
|
|
283
|
+
const frame = mockRTCEncodedVideoFrame(
|
|
284
|
+
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
input.write(frame);
|
|
288
|
+
await vitest.waitFor(() => expect(output.chunks).toHaveLength(1));
|
|
289
|
+
|
|
290
|
+
const extracted = extractPacketTrailer(frame.data);
|
|
291
|
+
expect(extracted.metadata).toBeUndefined();
|
|
292
|
+
expect(extracted.data.byteLength).toBe(frame.data.byteLength);
|
|
293
|
+
|
|
294
|
+
const frameTrailer = new Uint8Array(frame.data.slice(frame.data.byteLength - 2));
|
|
295
|
+
expect(frameTrailer[0]).toEqual(IV_LENGTH);
|
|
296
|
+
expect(frameTrailer[1]).toEqual(1);
|
|
297
|
+
} finally {
|
|
298
|
+
vitest.useRealTimers();
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('appends only timestamp packet trailer metadata after encryption', async () => {
|
|
303
|
+
vitest.useFakeTimers();
|
|
304
|
+
const now = new Date('2025-04-10T12:00:00.123Z');
|
|
305
|
+
vitest.setSystemTime(now);
|
|
306
|
+
try {
|
|
307
|
+
const { keys, input, output } = prepareParticipantTestEncoder(
|
|
308
|
+
participantIdentity,
|
|
309
|
+
{},
|
|
310
|
+
{ timestamp: true },
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
await keys.setKey(await createKeyMaterialFromString('key1'), 1);
|
|
314
|
+
|
|
315
|
+
const frame = mockRTCEncodedVideoFrame(
|
|
316
|
+
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
input.write(frame);
|
|
320
|
+
await vitest.waitFor(() => expect(output.chunks).toHaveLength(1));
|
|
321
|
+
|
|
322
|
+
const extracted = extractPacketTrailer(frame.data);
|
|
323
|
+
expect(frame.data.byteLength - extracted.data.byteLength).toBe(15);
|
|
324
|
+
expect(extracted.metadata?.userTimestamp).toBeGreaterThanOrEqual(
|
|
325
|
+
BigInt(now.getTime()) * BigInt(1000),
|
|
326
|
+
);
|
|
327
|
+
expect(extracted.metadata?.frameId).toBe(0);
|
|
328
|
+
} finally {
|
|
329
|
+
vitest.useRealTimers();
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('appends only frame id packet trailer metadata after encryption', async () => {
|
|
334
|
+
const { keys, input, output } = prepareParticipantTestEncoder(
|
|
335
|
+
participantIdentity,
|
|
336
|
+
{},
|
|
337
|
+
{ frameId: true },
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
await keys.setKey(await createKeyMaterialFromString('key1'), 1);
|
|
341
|
+
|
|
342
|
+
const frame = mockRTCEncodedVideoFrame(
|
|
343
|
+
new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
input.write(frame);
|
|
347
|
+
await vitest.waitFor(() => expect(output.chunks).toHaveLength(1));
|
|
348
|
+
|
|
349
|
+
const extracted = extractPacketTrailer(frame.data);
|
|
350
|
+
expect(frame.data.byteLength - extracted.data.byteLength).toBe(11);
|
|
351
|
+
expect(extracted.metadata).toEqual({
|
|
352
|
+
userTimestamp: BigInt(0),
|
|
353
|
+
frameId: 1,
|
|
354
|
+
});
|
|
355
|
+
});
|
|
218
356
|
});
|
|
219
357
|
|
|
220
358
|
describe('decode', () => {
|
|
@@ -391,6 +529,41 @@ describe('FrameCryptor', () => {
|
|
|
391
529
|
}
|
|
392
530
|
});
|
|
393
531
|
|
|
532
|
+
it('posts packet trailer metadata when the advertised trailer path is active', async () => {
|
|
533
|
+
vitest.useFakeTimers();
|
|
534
|
+
try {
|
|
535
|
+
const { cryptor, input, output } = prepareParticipantTestDecoder(participantIdentity, {});
|
|
536
|
+
const postMessage = vitest.fn();
|
|
537
|
+
vitest.stubGlobal('postMessage', postMessage);
|
|
538
|
+
encryptionEnabledMap.set(participantIdentity, false);
|
|
539
|
+
cryptor.setHasPacketTrailer(true);
|
|
540
|
+
|
|
541
|
+
const payload = Uint8Array.from([1, 2, 3, 4]);
|
|
542
|
+
const trailer = appendPacketTrailer(payload, 1_744_249_600_123_456n, 0);
|
|
543
|
+
const frame = mockRTCEncodedVideoFrame(trailer);
|
|
544
|
+
|
|
545
|
+
input.write(frame);
|
|
546
|
+
await vitest.advanceTimersToNextTimerAsync();
|
|
547
|
+
|
|
548
|
+
expect(new Uint8Array(output.chunks[0].data)).toEqual(payload);
|
|
549
|
+
expect(postMessage).toHaveBeenCalledWith({
|
|
550
|
+
kind: 'packetTrailerMetadata',
|
|
551
|
+
data: {
|
|
552
|
+
trackId: 'testTrack',
|
|
553
|
+
rtpTimestamp: frame.timestamp,
|
|
554
|
+
ssrc: 0,
|
|
555
|
+
metadata: {
|
|
556
|
+
userTimestamp: 1_744_249_600_123_456n,
|
|
557
|
+
frameId: 0,
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
} finally {
|
|
562
|
+
vitest.unstubAllGlobals();
|
|
563
|
+
vitest.useRealTimers();
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
394
567
|
it('recovers from delayed use of rotated key', async () => {
|
|
395
568
|
vitest.useFakeTimers();
|
|
396
569
|
try {
|