livekit-client 2.20.0 → 2.20.2
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 +41 -11
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +744 -178
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.fm.worker.js +1 -1
- package/dist/livekit-client.fm.worker.js.map +1 -1
- package/dist/livekit-client.fm.worker.mjs +41 -7
- package/dist/livekit-client.fm.worker.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +1 -0
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +1 -0
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +1 -0
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts +1 -0
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +1 -0
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +1 -0
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/sifPayload.d.ts +1 -0
- package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
- package/dist/src/frameMetadata/frameMetadata.d.ts +1 -0
- package/dist/src/frameMetadata/frameMetadata.d.ts.map +1 -1
- package/dist/src/frameMetadata/types.d.ts +1 -0
- package/dist/src/frameMetadata/types.d.ts.map +1 -1
- package/dist/src/logger.d.ts +3 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +42 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +1 -0
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +46 -4
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +1 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
- package/dist/src/room/data-track/frame.d.ts +1 -0
- package/dist/src/room/data-track/frame.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +7 -0
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/types.d.ts +1 -0
- package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/extensions.d.ts +1 -0
- package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/index.d.ts +1 -0
- package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/serializable.d.ts +1 -0
- package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +1 -0
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +1 -0
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts +7 -0
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +1 -0
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +1 -0
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +11 -1
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/record.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +1 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/signalToken.d.ts +26 -0
- package/dist/src/test/signalToken.d.ts.map +1 -0
- package/dist/src/type-polyfills/non-shared-typed-arrays.d.ts +2 -0
- package/dist/src/type-polyfills/non-shared-typed-arrays.d.ts.map +1 -0
- package/dist/src/utils/dataPacketBuffer.d.ts +19 -0
- package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +1 -0
- package/dist/ts4.2/e2ee/types.d.ts +1 -0
- package/dist/ts4.2/e2ee/utils.d.ts +1 -0
- package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +1 -0
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +1 -0
- package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +1 -0
- package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +1 -0
- package/dist/ts4.2/frameMetadata/frameMetadata.d.ts +1 -0
- package/dist/ts4.2/frameMetadata/types.d.ts +1 -0
- package/dist/ts4.2/logger.d.ts +3 -1
- package/dist/ts4.2/room/PCTransport.d.ts +42 -1
- package/dist/ts4.2/room/PCTransportManager.d.ts +1 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +46 -4
- package/dist/ts4.2/room/Room.d.ts +1 -0
- package/dist/ts4.2/room/data-track/frame.d.ts +1 -0
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +7 -0
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +1 -0
- package/dist/ts4.2/room/data-track/packet/extensions.d.ts +1 -0
- package/dist/ts4.2/room/data-track/packet/index.d.ts +1 -0
- package/dist/ts4.2/room/data-track/packet/serializable.d.ts +1 -0
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +1 -0
- package/dist/ts4.2/room/participant/Participant.d.ts +1 -0
- package/dist/ts4.2/room/participant/publishUtils.d.ts +7 -0
- package/dist/ts4.2/room/track/LocalAudioTrack.d.ts +1 -0
- package/dist/ts4.2/room/track/Track.d.ts +1 -0
- package/dist/ts4.2/room/track/options.d.ts +11 -1
- package/dist/ts4.2/room/utils.d.ts +1 -0
- package/dist/ts4.2/test/signalToken.d.ts +26 -0
- package/dist/ts4.2/type-polyfills/non-shared-typed-arrays.d.ts +2 -0
- package/dist/ts4.2/utils/dataPacketBuffer.d.ts +19 -0
- package/package.json +8 -4
- package/src/api/SignalClient.e2e.test.ts +264 -0
- package/src/api/SignalClient.ts +35 -2
- package/src/e2ee/E2eeManager.ts +3 -1
- package/src/e2ee/types.ts +1 -0
- package/src/e2ee/utils.ts +1 -0
- package/src/e2ee/worker/DataCryptor.ts +1 -0
- package/src/e2ee/worker/FrameCryptor.test.ts +1 -0
- package/src/e2ee/worker/FrameCryptor.ts +1 -0
- package/src/e2ee/worker/ParticipantKeyHandler.test.ts +1 -0
- package/src/e2ee/worker/e2ee.worker.ts +1 -0
- package/src/e2ee/worker/naluUtils.ts +1 -0
- package/src/e2ee/worker/sifPayload.ts +1 -0
- package/src/frameMetadata/frameMetadata.test.ts +83 -0
- package/src/frameMetadata/frameMetadata.ts +8 -0
- package/src/frameMetadata/types.ts +1 -0
- package/src/logger.ts +2 -0
- package/src/room/PCTransport.test.ts +150 -0
- package/src/room/PCTransport.ts +179 -31
- package/src/room/PCTransportManager.ts +10 -6
- package/src/room/RTCEngine.test.ts +275 -2
- package/src/room/RTCEngine.ts +277 -71
- package/src/room/RegionUrlProvider.ts +4 -2
- package/src/room/Room.ts +8 -1
- package/src/room/data-track/depacketizer.ts +1 -0
- package/src/room/data-track/frame.ts +1 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +303 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.ts +70 -8
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +2 -0
- package/src/room/data-track/outgoing/types.ts +1 -0
- package/src/room/data-track/packet/extensions.ts +1 -0
- package/src/room/data-track/packet/index.ts +1 -0
- package/src/room/data-track/packet/serializable.ts +1 -0
- package/src/room/errors.ts +4 -4
- package/src/room/participant/LocalParticipant.ts +17 -6
- package/src/room/participant/Participant.ts +6 -0
- package/src/room/participant/publishUtils.ts +14 -11
- package/src/room/track/LocalAudioTrack.ts +16 -0
- package/src/room/track/Track.ts +11 -6
- package/src/room/track/options.ts +11 -1
- package/src/room/track/record.ts +1 -0
- package/src/room/utils.ts +1 -0
- package/src/test/signalServerSetup.ts +151 -0
- package/src/test/signalToken.ts +59 -0
- package/src/test/vitest-context.d.ts +10 -0
- package/src/type-polyfills/{non-shared-typed-arrays.d.ts → non-shared-typed-arrays.ts} +6 -1
- package/src/utils/dataPacketBuffer.test.ts +100 -0
- package/src/utils/dataPacketBuffer.ts +48 -1
package/src/e2ee/E2eeManager.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Encryption_Type, TrackInfo } from '@livekit/protocol';
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
import type TypedEventEmitter from 'typed-emitter';
|
|
4
|
+
import type { FrameMetadata } from '../frameMetadata/types';
|
|
4
5
|
import { hasFrameMetadataPublishOptions } from '../frameMetadata/utils';
|
|
5
6
|
import log, { LogLevel, workerLogger } from '../logger';
|
|
6
7
|
import type RTCEngine from '../room/RTCEngine';
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
isScriptTransformSupportedForWorker,
|
|
21
22
|
isVideoTrack,
|
|
22
23
|
} from '../room/utils';
|
|
24
|
+
import type { NonSharedUint8Array } from '../type-polyfills/non-shared-typed-arrays';
|
|
23
25
|
import type { BaseKeyProvider } from './KeyProvider';
|
|
24
26
|
import { E2EE_FLAG } from './constants';
|
|
25
27
|
import { type E2EEManagerCallbacks, EncryptionEvent, KeyProviderEvent } from './events';
|
|
@@ -246,7 +248,7 @@ export class E2EEManager
|
|
|
246
248
|
trackId: string,
|
|
247
249
|
rtpTimestamp: number,
|
|
248
250
|
ssrc: number,
|
|
249
|
-
metadata:
|
|
251
|
+
metadata: FrameMetadata,
|
|
250
252
|
) {
|
|
251
253
|
if (!this.room) {
|
|
252
254
|
return;
|
package/src/e2ee/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { FrameMetadataPayload } from '../frameMetadata/frameMetadata';
|
|
|
2
2
|
import type { FrameMetadataPublishOptions } from '../frameMetadata/types';
|
|
3
3
|
import type { LogLevel } from '../logger';
|
|
4
4
|
import type { VideoCodec } from '../room/track/options';
|
|
5
|
+
import type { NonSharedUint8Array } from '../type-polyfills/non-shared-typed-arrays';
|
|
5
6
|
import type { BaseE2EEManager } from './E2eeManager';
|
|
6
7
|
import type { BaseKeyProvider } from './KeyProvider';
|
|
7
8
|
|
package/src/e2ee/utils.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DataPacket, EncryptedPacketPayload } from '@livekit/protocol';
|
|
2
|
+
import type { NonSharedUint8Array } from '../type-polyfills/non-shared-typed-arrays';
|
|
2
3
|
import { ENCRYPTION_ALGORITHM } from './constants';
|
|
3
4
|
import type { KeyProviderOptions } from './types';
|
|
4
5
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { workerLogger } from '../../logger';
|
|
2
|
+
import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
|
|
2
3
|
import { ENCRYPTION_ALGORITHM } from '../constants';
|
|
3
4
|
import { CryptorError, CryptorErrorReason } from '../errors';
|
|
4
5
|
import type { DecodeRatchetOptions, KeySet, RatchetResult } from '../types';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vitest } from 'vitest';
|
|
2
2
|
import { appendPacketTrailer, extractPacketTrailer } from '../../frameMetadata/frameMetadata';
|
|
3
3
|
import type { FrameMetadataPublishOptions } from '../../frameMetadata/types';
|
|
4
|
+
import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
|
|
4
5
|
import { IV_LENGTH, KEY_PROVIDER_DEFAULTS } from '../constants';
|
|
5
6
|
import { CryptorEvent } from '../events';
|
|
6
7
|
import type { KeyProviderOptions } from '../types';
|
|
@@ -10,6 +10,7 @@ import { hasFrameMetadataPublishOptions } from '../../frameMetadata/utils';
|
|
|
10
10
|
import { workerLogger } from '../../logger';
|
|
11
11
|
import { type VideoCodec, videoCodecs } from '../../room/track/options';
|
|
12
12
|
import { mimeTypeToVideoCodecString } from '../../room/track/utils';
|
|
13
|
+
import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
|
|
13
14
|
import { ENCRYPTION_ALGORITHM, IV_LENGTH, UNENCRYPTED_BYTES } from '../constants';
|
|
14
15
|
import { CryptorError, CryptorErrorReason } from '../errors';
|
|
15
16
|
import { type CryptorCallbacks, CryptorEvent } from '../events';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, test, vitest } from 'vitest';
|
|
2
|
+
import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
|
|
2
3
|
import { ENCRYPTION_ALGORITHM, KEY_PROVIDER_DEFAULTS } from '../constants';
|
|
3
4
|
import { KeyHandlerEvent } from '../events';
|
|
4
5
|
import { createKeyMaterialFromString, importKey } from '../utils';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { workerLogger } from '../../logger';
|
|
2
2
|
import type { VideoCodec } from '../../room/track/options';
|
|
3
|
+
import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
|
|
3
4
|
import { AsyncQueue } from '../../utils/AsyncQueue';
|
|
4
5
|
import { KEY_PROVIDER_DEFAULTS } from '../constants';
|
|
5
6
|
import { CryptorErrorReason } from '../errors';
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* NALU (Network Abstraction Layer Unit) utilities for H.264 and H.265 video processing
|
|
3
3
|
* Contains functions for parsing and working with NALUs in video frames
|
|
4
4
|
*/
|
|
5
|
+
import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Mask for extracting NALU type from H.264 header byte
|
|
@@ -1,11 +1,57 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import {
|
|
3
|
+
PACKET_TRAILER_ENVELOPE_SIZE,
|
|
4
|
+
PACKET_TRAILER_FRAME_ID_TAG,
|
|
5
|
+
PACKET_TRAILER_MAGIC,
|
|
6
|
+
PACKET_TRAILER_TIMESTAMP_TAG,
|
|
7
|
+
PACKET_TRAILER_USER_DATA_TAG,
|
|
3
8
|
appendPacketTrailer,
|
|
4
9
|
appendPacketTrailerToEncodedFrame,
|
|
5
10
|
extractPacketTrailer,
|
|
6
11
|
processPacketTrailer,
|
|
7
12
|
} from './frameMetadata';
|
|
8
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Builds a packet trailer (XORed TLVs + envelope) for the given fields. The
|
|
16
|
+
* SDK only writes timestamp/frameId, so this mirrors the native sender's TLV
|
|
17
|
+
* encoding to exercise user_data extraction.
|
|
18
|
+
*/
|
|
19
|
+
function buildTrailer(
|
|
20
|
+
payload: Uint8Array,
|
|
21
|
+
fields: { userTimestamp?: bigint; frameId?: number; userData?: Uint8Array },
|
|
22
|
+
): Uint8Array {
|
|
23
|
+
const tlvs: number[] = [];
|
|
24
|
+
|
|
25
|
+
if (fields.userTimestamp !== undefined) {
|
|
26
|
+
tlvs.push(PACKET_TRAILER_TIMESTAMP_TAG ^ 0xff, 8 ^ 0xff);
|
|
27
|
+
for (let i = 7; i >= 0; i -= 1) {
|
|
28
|
+
tlvs.push(Number((fields.userTimestamp >> BigInt(i * 8)) & BigInt(0xff)) ^ 0xff);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (fields.frameId !== undefined) {
|
|
33
|
+
tlvs.push(PACKET_TRAILER_FRAME_ID_TAG ^ 0xff, 4 ^ 0xff);
|
|
34
|
+
for (let i = 3; i >= 0; i -= 1) {
|
|
35
|
+
tlvs.push(((fields.frameId >> (i * 8)) & 0xff) ^ 0xff);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (fields.userData !== undefined) {
|
|
40
|
+
tlvs.push(PACKET_TRAILER_USER_DATA_TAG ^ 0xff, fields.userData.length ^ 0xff);
|
|
41
|
+
for (const byte of fields.userData) {
|
|
42
|
+
tlvs.push(byte ^ 0xff);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const trailerLength = tlvs.length + PACKET_TRAILER_ENVELOPE_SIZE;
|
|
47
|
+
const result = new Uint8Array(payload.length + trailerLength);
|
|
48
|
+
result.set(payload, 0);
|
|
49
|
+
result.set(tlvs, payload.length);
|
|
50
|
+
result[payload.length + tlvs.length] = trailerLength ^ 0xff;
|
|
51
|
+
result.set(PACKET_TRAILER_MAGIC, payload.length + tlvs.length + 1);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
9
55
|
describe('packetTrailer', () => {
|
|
10
56
|
afterEach(() => {
|
|
11
57
|
vi.useRealTimers();
|
|
@@ -47,6 +93,43 @@ describe('packetTrailer', () => {
|
|
|
47
93
|
});
|
|
48
94
|
});
|
|
49
95
|
|
|
96
|
+
it('extracts user_data from a trailer carrying only user_data', () => {
|
|
97
|
+
const payload = Uint8Array.from([1, 2, 3, 4]);
|
|
98
|
+
const userData = Uint8Array.from([0x00, 0x01, 0xfe, 0xff, 0x42]);
|
|
99
|
+
const trailer = buildTrailer(payload, { userData });
|
|
100
|
+
const extracted = extractPacketTrailer(trailer);
|
|
101
|
+
|
|
102
|
+
expect(Array.from(extracted.data)).toEqual(Array.from(payload));
|
|
103
|
+
expect(extracted.metadata?.userTimestamp).toBe(0n);
|
|
104
|
+
expect(extracted.metadata?.frameId).toBe(0);
|
|
105
|
+
expect(extracted.metadata?.userData).toBeInstanceOf(Uint8Array);
|
|
106
|
+
expect(Array.from(extracted.metadata!.userData!)).toEqual(Array.from(userData));
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('extracts user_data alongside timestamp and frameId', () => {
|
|
110
|
+
const payload = Uint8Array.from([9, 8, 7, 6, 5]);
|
|
111
|
+
const userData = Uint8Array.from([10, 20, 30, 40]);
|
|
112
|
+
const trailer = buildTrailer(payload, {
|
|
113
|
+
userTimestamp: 1_744_249_600_123_456n,
|
|
114
|
+
frameId: 42,
|
|
115
|
+
userData,
|
|
116
|
+
});
|
|
117
|
+
const extracted = extractPacketTrailer(trailer);
|
|
118
|
+
|
|
119
|
+
expect(Array.from(extracted.data)).toEqual(Array.from(payload));
|
|
120
|
+
expect(extracted.metadata?.userTimestamp).toBe(1_744_249_600_123_456n);
|
|
121
|
+
expect(extracted.metadata?.frameId).toBe(42);
|
|
122
|
+
expect(Array.from(extracted.metadata!.userData!)).toEqual(Array.from(userData));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('leaves userData undefined when no user_data tag is present', () => {
|
|
126
|
+
const payload = Uint8Array.from([1, 2, 3, 4]);
|
|
127
|
+
const trailer = appendPacketTrailer(payload, 1_744_249_600_123_456n, 42);
|
|
128
|
+
const extracted = extractPacketTrailer(trailer);
|
|
129
|
+
|
|
130
|
+
expect(extracted.metadata?.userData).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
|
|
50
133
|
it('returns data unchanged when both timestamp and frameId are 0', () => {
|
|
51
134
|
const payload = Uint8Array.from([1, 2, 3, 4]);
|
|
52
135
|
const result = appendPacketTrailer(payload, 0n, 0);
|
|
@@ -10,6 +10,7 @@ export const PACKET_TRAILER_MAGIC = Uint8Array.from([
|
|
|
10
10
|
|
|
11
11
|
export const PACKET_TRAILER_TIMESTAMP_TAG = 0x01;
|
|
12
12
|
export const PACKET_TRAILER_FRAME_ID_TAG = 0x02;
|
|
13
|
+
export const PACKET_TRAILER_USER_DATA_TAG = 0x03;
|
|
13
14
|
export const PACKET_TRAILER_ENVELOPE_SIZE = 5;
|
|
14
15
|
|
|
15
16
|
const TIMESTAMP_TLV_SIZE = 10;
|
|
@@ -127,6 +128,13 @@ export function extractPacketTrailer(data: ArrayBuffer | Uint8Array): ExtractPac
|
|
|
127
128
|
} else if (tag === PACKET_TRAILER_FRAME_ID_TAG && length === 4) {
|
|
128
129
|
metadata.frameId = readUint32Xor(bytes, offset, length);
|
|
129
130
|
foundAny = true;
|
|
131
|
+
} else if (tag === PACKET_TRAILER_USER_DATA_TAG) {
|
|
132
|
+
const userData = new Uint8Array(length);
|
|
133
|
+
for (let index = 0; index < length; index += 1) {
|
|
134
|
+
userData[index] = bytes[offset + index] ^ 0xff;
|
|
135
|
+
}
|
|
136
|
+
metadata.userData = userData;
|
|
137
|
+
foundAny = true;
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
offset += length;
|
package/src/logger.ts
CHANGED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { type MediaDescription, parse } from 'sdp-transform';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { conformBundledCodecFmtp, placeholderMidsFromTransceivers } from './PCTransport';
|
|
4
|
+
|
|
5
|
+
/** Parse the `key[=value]` pairs of an fmtp config into a comparable set. */
|
|
6
|
+
const paramSet = (config: string) => new Set(config.split(';').filter(Boolean));
|
|
7
|
+
|
|
8
|
+
const fmtpOf = (media: MediaDescription[], mid: string, payload: number) => {
|
|
9
|
+
const m = media.find((section) => `${section.mid}` === mid)!;
|
|
10
|
+
return m.fmtp.find((f) => f.payload === payload)?.config;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Predicate for conformBundledCodecFmtp based on an explicit set of mids. */
|
|
14
|
+
const placeholders = (mids: string[]) => {
|
|
15
|
+
const set = new Set(mids);
|
|
16
|
+
return (m: MediaDescription) => set.has(`${m.mid}`);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// One publisher bundle: a published mic + camera (real sends, with msid/ssrc)
|
|
20
|
+
// and their pre-populated recvonly placeholders.
|
|
21
|
+
const PUBLISHER_SDP = `v=0
|
|
22
|
+
o=- 0 0 IN IP4 127.0.0.1
|
|
23
|
+
s=-
|
|
24
|
+
t=0 0
|
|
25
|
+
a=group:BUNDLE 0 1 2 3
|
|
26
|
+
m=audio 9 UDP/TLS/RTP/SAVPF 111
|
|
27
|
+
c=IN IP4 0.0.0.0
|
|
28
|
+
a=mid:0
|
|
29
|
+
a=sendonly
|
|
30
|
+
a=msid:s mic
|
|
31
|
+
a=ssrc:1111 cname:a
|
|
32
|
+
a=rtpmap:111 opus/48000/2
|
|
33
|
+
a=fmtp:111 minptime=10;useinbandfec=1;usedtx=1
|
|
34
|
+
m=audio 9 UDP/TLS/RTP/SAVPF 111
|
|
35
|
+
c=IN IP4 0.0.0.0
|
|
36
|
+
a=mid:1
|
|
37
|
+
a=recvonly
|
|
38
|
+
a=rtpmap:111 opus/48000/2
|
|
39
|
+
a=fmtp:111 minptime=10;useinbandfec=1
|
|
40
|
+
m=video 9 UDP/TLS/RTP/SAVPF 49
|
|
41
|
+
c=IN IP4 0.0.0.0
|
|
42
|
+
a=mid:2
|
|
43
|
+
a=sendonly
|
|
44
|
+
a=msid:s cam
|
|
45
|
+
a=ssrc:2222 cname:b
|
|
46
|
+
a=rtpmap:49 H265/90000
|
|
47
|
+
a=fmtp:49 level-id=186;profile-id=1;tier-flag=0;tx-mode=SRST
|
|
48
|
+
m=video 9 UDP/TLS/RTP/SAVPF 49
|
|
49
|
+
c=IN IP4 0.0.0.0
|
|
50
|
+
a=mid:3
|
|
51
|
+
a=recvonly
|
|
52
|
+
a=rtpmap:49 H265/90000
|
|
53
|
+
a=fmtp:49 level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST`;
|
|
54
|
+
|
|
55
|
+
describe('placeholderMidsFromTransceivers', () => {
|
|
56
|
+
const tr = (mid: string | null, track: unknown) =>
|
|
57
|
+
({ mid, sender: { track } }) as unknown as RTCRtpTransceiver;
|
|
58
|
+
|
|
59
|
+
it('includes transceivers with a mid and no outgoing track', () => {
|
|
60
|
+
const mids = placeholderMidsFromTransceivers([
|
|
61
|
+
tr('0', {}), // real send
|
|
62
|
+
tr('1', null), // pre-populated placeholder
|
|
63
|
+
tr('2', null), // reverted after unpublish (msid may linger in SDP, but no track)
|
|
64
|
+
tr(null, null), // not yet negotiated — no mid, excluded
|
|
65
|
+
]);
|
|
66
|
+
expect(mids).toEqual(new Set(['1', '2']));
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('conformBundledCodecFmtp', () => {
|
|
71
|
+
it('copies opus and H.265 fmtp from real sections onto placeholders', () => {
|
|
72
|
+
const { media } = parse(PUBLISHER_SDP);
|
|
73
|
+
|
|
74
|
+
conformBundledCodecFmtp(media, placeholders(['1', '3']));
|
|
75
|
+
|
|
76
|
+
expect(fmtpOf(media, '1', 111)).toBe(fmtpOf(media, '0', 111));
|
|
77
|
+
expect(paramSet(fmtpOf(media, '1', 111)!)).toContain('usedtx=1');
|
|
78
|
+
expect(fmtpOf(media, '3', 49)).toBe(fmtpOf(media, '2', 49));
|
|
79
|
+
expect(paramSet(fmtpOf(media, '3', 49)!)).toContain('level-id=186');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('never rewrites the real (non-placeholder) sections', () => {
|
|
83
|
+
const { media } = parse(PUBLISHER_SDP);
|
|
84
|
+
|
|
85
|
+
conformBundledCodecFmtp(media, placeholders(['1', '3']));
|
|
86
|
+
|
|
87
|
+
expect(fmtpOf(media, '0', 111)).toBe('minptime=10;useinbandfec=1;usedtx=1');
|
|
88
|
+
expect(paramSet(fmtpOf(media, '2', 49)!)).toContain('level-id=186');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('converges divergent placeholders even when no real section declares the payload', () => {
|
|
92
|
+
// Two placeholders that disagree (a reverted one kept level 186, a fresh one
|
|
93
|
+
// has 180) with no active send present.
|
|
94
|
+
const { media } = parse(`v=0
|
|
95
|
+
o=- 0 0 IN IP4 127.0.0.1
|
|
96
|
+
s=-
|
|
97
|
+
t=0 0
|
|
98
|
+
a=group:BUNDLE 10 11
|
|
99
|
+
m=video 9 UDP/TLS/RTP/SAVPF 49
|
|
100
|
+
a=mid:10
|
|
101
|
+
a=rtpmap:49 H265/90000
|
|
102
|
+
a=fmtp:49 level-id=186;profile-id=1;tier-flag=0;tx-mode=SRST
|
|
103
|
+
m=video 9 UDP/TLS/RTP/SAVPF 49
|
|
104
|
+
a=mid:11
|
|
105
|
+
a=rtpmap:49 H265/90000
|
|
106
|
+
a=fmtp:49 level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST`);
|
|
107
|
+
|
|
108
|
+
conformBundledCodecFmtp(media, placeholders(['10', '11']));
|
|
109
|
+
|
|
110
|
+
expect(fmtpOf(media, '10', 49)).toBe(fmtpOf(media, '11', 49));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('conforms a reverted section that kept its msid (second-publish case)', () => {
|
|
114
|
+
// The real second publish: a live camera send (level 186), plus the section
|
|
115
|
+
// left over from the first (unpublished) track. Chrome keeps that section's
|
|
116
|
+
// `a=msid`/`a=ssrc` even though it no longer sends, so it must be identified
|
|
117
|
+
// as a placeholder by its transceiver (no track), not by SDP.
|
|
118
|
+
const { media } = parse(`v=0
|
|
119
|
+
o=- 0 0 IN IP4 127.0.0.1
|
|
120
|
+
s=-
|
|
121
|
+
t=0 0
|
|
122
|
+
a=group:BUNDLE 3 4 5
|
|
123
|
+
m=video 9 UDP/TLS/RTP/SAVPF 49
|
|
124
|
+
a=mid:3
|
|
125
|
+
a=sendonly
|
|
126
|
+
a=msid:s cam
|
|
127
|
+
a=ssrc:9 cname:c
|
|
128
|
+
a=rtpmap:49 H265/90000
|
|
129
|
+
a=fmtp:49 level-id=186;profile-id=1;tier-flag=0;tx-mode=SRST
|
|
130
|
+
m=video 9 UDP/TLS/RTP/SAVPF 49
|
|
131
|
+
a=mid:4
|
|
132
|
+
a=sendonly
|
|
133
|
+
a=msid:s stale
|
|
134
|
+
a=ssrc:8 cname:d
|
|
135
|
+
a=rtpmap:49 H265/90000
|
|
136
|
+
a=fmtp:49 level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST
|
|
137
|
+
m=video 9 UDP/TLS/RTP/SAVPF 49
|
|
138
|
+
a=mid:5
|
|
139
|
+
a=recvonly
|
|
140
|
+
a=rtpmap:49 H265/90000
|
|
141
|
+
a=fmtp:49 level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST`);
|
|
142
|
+
|
|
143
|
+
// mid 4 still has msid/ssrc in the SDP, but its transceiver has no track.
|
|
144
|
+
conformBundledCodecFmtp(media, placeholders(['4', '5']));
|
|
145
|
+
|
|
146
|
+
expect(paramSet(fmtpOf(media, '3', 49)!)).toContain('level-id=186'); // real send untouched
|
|
147
|
+
expect(fmtpOf(media, '4', 49)).toBe(fmtpOf(media, '3', 49)); // stale conformed
|
|
148
|
+
expect(fmtpOf(media, '5', 49)).toBe(fmtpOf(media, '3', 49)); // placeholder conformed
|
|
149
|
+
});
|
|
150
|
+
});
|