livekit-client 2.18.9 → 2.18.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +5609 -644
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +2870 -2420
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.pt.worker.js +2 -0
- package/dist/livekit-client.pt.worker.js.map +1 -0
- package/dist/livekit-client.pt.worker.mjs +5834 -0
- package/dist/livekit-client.pt.worker.mjs.map +1 -0
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts +2 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +8 -7
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +35 -8
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +5 -5
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts +5 -5
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +21 -4
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/sifPayload.d.ts +7 -7
- package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/options.d.ts +7 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/packetTrailer/PacketTrailerManager.d.ts +49 -0
- package/dist/src/packetTrailer/PacketTrailerManager.d.ts.map +1 -0
- package/dist/src/packetTrailer/packetTrailer.d.ts +32 -0
- package/dist/src/packetTrailer/packetTrailer.d.ts.map +1 -0
- package/dist/src/packetTrailer/types.d.ts +57 -0
- package/dist/src/packetTrailer/types.d.ts.map +1 -0
- package/dist/src/packetTrailer/utils.d.ts +9 -0
- package/dist/src/packetTrailer/utils.d.ts.map +1 -0
- package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
- package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts.map +1 -0
- package/dist/src/room/RTCEngine.d.ts +2 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +3 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-track/RemoteDataTrack.d.ts +5 -1
- package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
- package/dist/src/room/data-track/depacketizer.d.ts +12 -4
- package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
- package/dist/src/room/data-track/frame.d.ts +3 -3
- package/dist/src/room/data-track/frame.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/pipeline.d.ts +4 -1
- package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/types.d.ts +2 -2
- package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/extensions.d.ts +4 -4
- package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/index.d.ts +5 -5
- package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/serializable.d.ts +1 -1
- package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
- package/dist/src/room/data-track/types.d.ts +7 -0
- package/dist/src/room/data-track/types.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +2 -2
- package/dist/src/room/participant/LocalParticipant.d.ts +3 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +1 -1
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/track/PacketTrailerExtractor.d.ts +19 -0
- package/dist/src/room/track/PacketTrailerExtractor.d.ts.map +1 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts +16 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +1 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/create.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +10 -0
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +4 -3
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -1
- package/dist/src/utils/dataPacketBuffer.d.ts +1 -1
- package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/ts4.2/api/SignalClient.d.ts +2 -1
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +8 -7
- package/dist/ts4.2/e2ee/types.d.ts +35 -8
- package/dist/ts4.2/e2ee/utils.d.ts +5 -5
- package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +5 -5
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +21 -4
- package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +1 -1
- package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +7 -7
- package/dist/ts4.2/index.d.ts +5 -1
- package/dist/ts4.2/options.d.ts +7 -0
- package/dist/ts4.2/packetTrailer/PacketTrailerManager.d.ts +49 -0
- package/dist/ts4.2/packetTrailer/packetTrailer.d.ts +32 -0
- package/dist/ts4.2/packetTrailer/types.d.ts +57 -0
- package/dist/ts4.2/packetTrailer/utils.d.ts +9 -0
- package/dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +2 -1
- package/dist/ts4.2/room/Room.d.ts +3 -1
- package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +5 -1
- package/dist/ts4.2/room/data-track/depacketizer.d.ts +12 -4
- package/dist/ts4.2/room/data-track/frame.d.ts +3 -3
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
- package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +4 -1
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +2 -2
- package/dist/ts4.2/room/data-track/packet/extensions.d.ts +4 -4
- package/dist/ts4.2/room/data-track/packet/index.d.ts +5 -5
- package/dist/ts4.2/room/data-track/packet/serializable.d.ts +1 -1
- package/dist/ts4.2/room/data-track/types.d.ts +7 -0
- package/dist/ts4.2/room/events.d.ts +2 -2
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +3 -1
- package/dist/ts4.2/room/participant/Participant.d.ts +1 -1
- package/dist/ts4.2/room/track/PacketTrailerExtractor.d.ts +19 -0
- package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +16 -0
- package/dist/ts4.2/room/track/Track.d.ts +1 -1
- package/dist/ts4.2/room/track/options.d.ts +10 -0
- package/dist/ts4.2/room/utils.d.ts +4 -3
- package/dist/ts4.2/utils/dataPacketBuffer.d.ts +1 -1
- package/dist/ts4.2/version.d.ts +1 -1
- package/package.json +24 -16
- package/src/api/SignalClient.test.ts +102 -10
- package/src/api/SignalClient.ts +4 -2
- package/src/api/WebSocketStream.test.ts +0 -1
- package/src/e2ee/E2eeManager.ts +82 -30
- package/src/e2ee/types.ts +37 -8
- package/src/e2ee/utils.ts +7 -6
- package/src/e2ee/worker/DataCryptor.ts +6 -6
- package/src/e2ee/worker/FrameCryptor.test.ts +177 -4
- package/src/e2ee/worker/FrameCryptor.ts +94 -14
- package/src/e2ee/worker/ParticipantKeyHandler.test.ts +4 -4
- package/src/e2ee/worker/e2ee.worker.ts +13 -5
- package/src/e2ee/worker/naluUtils.ts +4 -4
- package/src/e2ee/worker/sifPayload.ts +10 -8
- package/src/index.ts +7 -0
- package/src/options.ts +8 -0
- package/src/packetTrailer/PacketTrailerManager.test.ts +172 -0
- package/src/packetTrailer/PacketTrailerManager.ts +250 -0
- package/src/packetTrailer/packetTrailer.test.ts +174 -0
- package/src/packetTrailer/packetTrailer.ts +276 -0
- package/src/packetTrailer/types.ts +75 -0
- package/src/packetTrailer/utils.test.ts +105 -0
- package/src/packetTrailer/utils.ts +50 -0
- package/src/packetTrailer/worker/packetTrailer.worker.ts +155 -0
- package/src/packetTrailer/worker/tsconfig.json +14 -0
- package/src/room/BackOffStrategy.test.ts +1 -1
- package/src/room/RTCEngine.test.ts +219 -0
- package/src/room/RTCEngine.ts +86 -20
- package/src/room/Room.test.ts +62 -1
- package/src/room/Room.ts +28 -5
- package/src/room/data-track/RemoteDataTrack.ts +8 -1
- package/src/room/data-track/depacketizer.test.ts +433 -1
- package/src/room/data-track/depacketizer.ts +79 -61
- package/src/room/data-track/frame.ts +2 -2
- package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +194 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.ts +21 -1
- package/src/room/data-track/incoming/pipeline.ts +13 -2
- package/src/room/data-track/outgoing/types.ts +3 -2
- package/src/room/data-track/packet/extensions.ts +2 -2
- package/src/room/data-track/packet/index.ts +6 -6
- package/src/room/data-track/packet/serializable.ts +1 -1
- package/src/room/data-track/types.ts +8 -0
- package/src/room/events.ts +2 -2
- package/src/room/participant/LocalParticipant.test.ts +81 -0
- package/src/room/participant/LocalParticipant.ts +48 -7
- package/src/room/participant/Participant.ts +1 -1
- package/src/room/participant/publishUtils.ts +1 -1
- package/src/room/track/PacketTrailerExtractor.ts +43 -0
- package/src/room/track/RemoteVideoTrack.ts +23 -2
- package/src/room/track/Track.ts +1 -1
- package/src/room/track/create.ts +0 -4
- package/src/room/track/options.ts +11 -0
- package/src/room/track/record.ts +1 -1
- package/src/room/track/utils.ts +4 -1
- package/src/room/utils.test.ts +14 -1
- package/src/room/utils.ts +17 -3
- package/src/test/MockMediaStreamTrack.ts +0 -1
- package/src/type-polyfills/non-shared-typed-arrays.d.ts +6 -0
- package/src/utils/dataPacketBuffer.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import type { TrackInfo } from '@livekit/protocol';
|
|
2
|
+
import log from '../logger';
|
|
3
|
+
import type Room from '../room/Room';
|
|
4
|
+
import { RoomEvent } from '../room/events';
|
|
5
|
+
import { PacketTrailerExtractor } from '../room/track/PacketTrailerExtractor';
|
|
6
|
+
import type RemoteTrack from '../room/track/RemoteTrack';
|
|
7
|
+
import RemoteVideoTrack from '../room/track/RemoteVideoTrack';
|
|
8
|
+
import type { PTDecodeMessage, PTUpdateTrackIdMessage, PTWorkerMessage } from './types';
|
|
9
|
+
import { isPacketTrailerSupported, shouldUsePacketTrailerScriptTransform } from './utils';
|
|
10
|
+
|
|
11
|
+
export interface PacketTrailerOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Dedicated worker for extracting packet trailers off the main thread.
|
|
14
|
+
*
|
|
15
|
+
* Encoded video streams are transferred to the worker for processing, which
|
|
16
|
+
* avoids per-frame work on the main thread.
|
|
17
|
+
*/
|
|
18
|
+
worker: Worker;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Manages packet trailer extraction for received video tracks.
|
|
23
|
+
*
|
|
24
|
+
* When a track's TrackInfo indicates packet trailer features, the manager
|
|
25
|
+
* wires up an encoded frame transform to strip the trailer from encoded frames
|
|
26
|
+
* and cache the metadata for lookup.
|
|
27
|
+
*
|
|
28
|
+
* Packet trailer extraction is worker-only. If no worker is configured, the
|
|
29
|
+
* SDK does not advertise packet trailer support and skips extraction.
|
|
30
|
+
*
|
|
31
|
+
* When E2EE is active, the E2EE FrameCryptor worker handles trailer
|
|
32
|
+
* extraction directly (before decryption), so this manager only creates
|
|
33
|
+
* the extractor/metadata cache — no separate pipeline is installed.
|
|
34
|
+
*
|
|
35
|
+
* @experimental
|
|
36
|
+
*/
|
|
37
|
+
export class PacketTrailerManager {
|
|
38
|
+
private worker?: Worker;
|
|
39
|
+
|
|
40
|
+
private room?: Room;
|
|
41
|
+
|
|
42
|
+
private extractors = new Map<string, PacketTrailerExtractor>();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Tracks the trackId associated with each receiver that has had its
|
|
46
|
+
* encoded streams handed off to the worker. Used to detect receiver
|
|
47
|
+
* reuse (transceiver recycling) so we can remap trackIds instead of
|
|
48
|
+
* re-transferring already-consumed streams.
|
|
49
|
+
*/
|
|
50
|
+
private workerPipelines = new Map<RTCRtpReceiver, string>();
|
|
51
|
+
|
|
52
|
+
constructor(options?: PacketTrailerOptions) {
|
|
53
|
+
this.worker = options?.worker;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @internal */
|
|
57
|
+
setup(room: Room) {
|
|
58
|
+
if (room === this.room) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.room = room;
|
|
62
|
+
|
|
63
|
+
if (this.worker) {
|
|
64
|
+
this.worker.onmessage = this.onWorkerMessage;
|
|
65
|
+
this.worker.onerror = this.onWorkerError;
|
|
66
|
+
this.worker.postMessage({ kind: 'init' });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
room
|
|
70
|
+
.on(RoomEvent.TrackSubscribed, (track, pub, _participant) => {
|
|
71
|
+
if (track.kind !== 'video') {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.setupReceiver(track as unknown as RemoteVideoTrack, pub.trackInfo);
|
|
75
|
+
})
|
|
76
|
+
.on(RoomEvent.TrackUnsubscribed, (track) => {
|
|
77
|
+
this.teardownTrack(track);
|
|
78
|
+
})
|
|
79
|
+
.on(RoomEvent.Disconnected, () => {
|
|
80
|
+
this.cleanup();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private setupReceiver(track: RemoteVideoTrack, trackInfo?: TrackInfo) {
|
|
85
|
+
const receiver = track.receiver;
|
|
86
|
+
if (!receiver) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Only install a pipeline for tracks that actually advertise packet
|
|
91
|
+
// trailer features. This keeps us out of the way for tracks published by
|
|
92
|
+
// clients on older protocols or that don't opt into the feature.
|
|
93
|
+
const hasFeatures =
|
|
94
|
+
!!trackInfo?.packetTrailerFeatures && trackInfo.packetTrailerFeatures.length > 0;
|
|
95
|
+
if (!hasFeatures) {
|
|
96
|
+
if (!this.room?.hasE2EESetup) {
|
|
97
|
+
this.setupPassthroughReceiver(receiver, track.mediaStreamID);
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (
|
|
103
|
+
!isPacketTrailerSupported(this.worker ? { worker: this.worker } : undefined) &&
|
|
104
|
+
!this.room?.hasE2EESetup
|
|
105
|
+
) {
|
|
106
|
+
log.warn('packet trailer transform not supported; skipping extraction');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const extractor = new PacketTrailerExtractor();
|
|
111
|
+
const trackId = track.mediaStreamID;
|
|
112
|
+
|
|
113
|
+
this.extractors.set(trackId, extractor);
|
|
114
|
+
track.packetTrailerExtractor = extractor;
|
|
115
|
+
|
|
116
|
+
if (this.room?.hasE2EESetup) {
|
|
117
|
+
// E2EE worker strips the trailer and injects metadata directly into
|
|
118
|
+
// the extractor via E2eeManager; no pipeline is needed here.
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.setupWorkerReceiver(receiver, trackId, true);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private setupPassthroughReceiver(receiver: RTCRtpReceiver, trackId: string) {
|
|
126
|
+
if (shouldUsePacketTrailerScriptTransform()) {
|
|
127
|
+
if ('transform' in receiver) {
|
|
128
|
+
// @ts-ignore
|
|
129
|
+
receiver.transform = null;
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
this.worker &&
|
|
136
|
+
isPacketTrailerSupported({ worker: this.worker }) &&
|
|
137
|
+
!this.workerPipelines.has(receiver)
|
|
138
|
+
) {
|
|
139
|
+
this.setupWorkerReceiver(receiver, trackId, false);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (this.worker && this.workerPipelines.has(receiver)) {
|
|
144
|
+
this.setupWorkerReceiver(receiver, trackId, false);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private setupWorkerReceiver(
|
|
149
|
+
receiver: RTCRtpReceiver,
|
|
150
|
+
newTrackId: string,
|
|
151
|
+
hasPacketTrailer = true,
|
|
152
|
+
) {
|
|
153
|
+
const worker = this.worker;
|
|
154
|
+
if (!worker) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (shouldUsePacketTrailerScriptTransform()) {
|
|
159
|
+
// @ts-ignore
|
|
160
|
+
receiver.transform = new RTCRtpScriptTransform(worker, {
|
|
161
|
+
kind: 'decode',
|
|
162
|
+
trackId: newTrackId,
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const existingTrackId = this.workerPipelines.get(receiver);
|
|
168
|
+
|
|
169
|
+
if (existingTrackId) {
|
|
170
|
+
// Receiver is reused (transceiver recycled). The worker already owns
|
|
171
|
+
// the encoded streams — just remap the trackId so metadata is keyed
|
|
172
|
+
// correctly and re-activate processing.
|
|
173
|
+
const msg: PTUpdateTrackIdMessage = {
|
|
174
|
+
kind: 'updateTrackId',
|
|
175
|
+
data: { oldTrackId: existingTrackId, newTrackId, hasPacketTrailer },
|
|
176
|
+
};
|
|
177
|
+
worker.postMessage(msg);
|
|
178
|
+
this.workerPipelines.set(receiver, newTrackId);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!('createEncodedStreams' in receiver)) {
|
|
183
|
+
log.warn('createEncodedStreams not supported');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let streams: { readable: ReadableStream; writable: WritableStream };
|
|
188
|
+
try {
|
|
189
|
+
// @ts-ignore — createEncodedStreams is not in standard typings
|
|
190
|
+
streams = receiver.createEncodedStreams();
|
|
191
|
+
} catch (err) {
|
|
192
|
+
log.warn('failed to create encoded streams', { error: err });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const msg: PTDecodeMessage = {
|
|
197
|
+
kind: 'decode',
|
|
198
|
+
data: {
|
|
199
|
+
readableStream: streams.readable,
|
|
200
|
+
writableStream: streams.writable,
|
|
201
|
+
trackId: newTrackId,
|
|
202
|
+
hasPacketTrailer,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
worker.postMessage(msg, [streams.readable, streams.writable]);
|
|
206
|
+
this.workerPipelines.set(receiver, newTrackId);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private teardownTrack(track: RemoteTrack) {
|
|
210
|
+
const trackId = track.mediaStreamID;
|
|
211
|
+
const extractor = this.extractors.get(trackId);
|
|
212
|
+
if (extractor) {
|
|
213
|
+
extractor.dispose();
|
|
214
|
+
this.extractors.delete(trackId);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (track instanceof RemoteVideoTrack) {
|
|
218
|
+
track.packetTrailerExtractor = undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// The receiver pipeline is intentionally left running. If the receiver is
|
|
222
|
+
// reused for a new track, `setupReceiver` will remap it. If the room
|
|
223
|
+
// disconnects, `cleanup` drops all state. Any metadata produced in the
|
|
224
|
+
// meantime is harmless — the extractor above has already been disposed and
|
|
225
|
+
// is no longer reachable from any track.
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private cleanup() {
|
|
229
|
+
for (const extractor of this.extractors.values()) {
|
|
230
|
+
extractor.dispose();
|
|
231
|
+
}
|
|
232
|
+
this.extractors.clear();
|
|
233
|
+
this.workerPipelines.clear();
|
|
234
|
+
this.worker?.terminate();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private onWorkerMessage = (ev: MessageEvent<PTWorkerMessage>) => {
|
|
238
|
+
const msg = ev.data;
|
|
239
|
+
if (msg.kind === 'metadata') {
|
|
240
|
+
const extractor = this.extractors.get(msg.data.trackId);
|
|
241
|
+
if (extractor) {
|
|
242
|
+
extractor.storeMetadata(msg.data.rtpTimestamp, msg.data.ssrc, msg.data.metadata);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
private onWorkerError = (ev: ErrorEvent) => {
|
|
248
|
+
log.error('packet trailer worker encountered an error:', { error: ev.error });
|
|
249
|
+
};
|
|
250
|
+
}
|