livekit-client 2.19.2 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +23 -23
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +179 -111
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.fm.worker.js +2 -0
- package/dist/{livekit-client.pt.worker.js.map → livekit-client.fm.worker.js.map} +1 -1
- package/dist/{livekit-client.pt.worker.mjs → livekit-client.fm.worker.mjs} +4 -4
- package/dist/livekit-client.fm.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.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +5 -5
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +8 -8
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/{packetTrailer/PacketTrailerManager.d.ts → frameMetadata/FrameMetadataManager.d.ts} +8 -4
- package/dist/src/{packetTrailer/PacketTrailerManager.d.ts.map → frameMetadata/FrameMetadataManager.d.ts.map} +1 -1
- package/dist/src/{packetTrailer/packetTrailer.d.ts → frameMetadata/frameMetadata.d.ts} +7 -7
- package/dist/src/frameMetadata/frameMetadata.d.ts.map +1 -0
- package/dist/src/{packetTrailer → frameMetadata}/types.d.ts +10 -6
- package/dist/src/frameMetadata/types.d.ts.map +1 -0
- package/dist/src/frameMetadata/utils.d.ts +9 -0
- package/dist/src/{packetTrailer → frameMetadata}/utils.d.ts.map +1 -1
- package/dist/src/frameMetadata/worker/frameMetadata.worker.d.ts +2 -0
- package/dist/src/frameMetadata/worker/frameMetadata.worker.d.ts.map +1 -0
- package/dist/src/index.d.ts +10 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/options.d.ts +8 -4
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +1 -0
- 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 +3 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +2 -2
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +2 -2
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/token-source/TokenSource.d.ts.map +1 -1
- package/dist/src/room/token-source/types.d.ts +1 -0
- package/dist/src/room/token-source/types.d.ts.map +1 -1
- package/dist/src/room/token-source/utils.d.ts.map +1 -1
- package/dist/src/room/track/FrameMetadataExtractor.d.ts +19 -0
- package/dist/src/room/track/FrameMetadataExtractor.d.ts.map +1 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts +7 -7
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +17 -5
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +1 -1
- package/dist/ts4.2/e2ee/types.d.ts +5 -5
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +8 -8
- package/dist/ts4.2/{packetTrailer/PacketTrailerManager.d.ts → frameMetadata/FrameMetadataManager.d.ts} +8 -4
- package/dist/ts4.2/{packetTrailer/packetTrailer.d.ts → frameMetadata/frameMetadata.d.ts} +7 -7
- package/dist/ts4.2/{packetTrailer → frameMetadata}/types.d.ts +10 -6
- package/dist/ts4.2/frameMetadata/utils.d.ts +9 -0
- package/dist/ts4.2/frameMetadata/worker/frameMetadata.worker.d.ts +2 -0
- package/dist/ts4.2/index.d.ts +9 -3
- package/dist/ts4.2/options.d.ts +8 -4
- package/dist/ts4.2/room/PCTransport.d.ts +1 -0
- package/dist/ts4.2/room/PCTransportManager.d.ts +1 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +3 -1
- package/dist/ts4.2/room/Room.d.ts +2 -2
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +2 -2
- package/dist/ts4.2/room/token-source/types.d.ts +1 -0
- package/dist/ts4.2/room/track/FrameMetadataExtractor.d.ts +19 -0
- package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +7 -7
- package/dist/ts4.2/room/track/options.d.ts +17 -5
- package/package.json +23 -18
- package/src/api/SignalClient.test.ts +4 -4
- package/src/api/SignalClient.ts +7 -3
- package/src/api/WebSocketStream.test.ts +7 -7
- package/src/e2ee/E2eeManager.ts +13 -11
- package/src/e2ee/types.ts +5 -5
- package/src/e2ee/worker/FrameCryptor.test.ts +5 -5
- package/src/e2ee/worker/FrameCryptor.ts +27 -23
- package/src/e2ee/worker/e2ee.worker.ts +5 -5
- package/src/e2ee/worker/tsconfig.json +4 -1
- package/src/{packetTrailer/PacketTrailerManager.test.ts → frameMetadata/FrameMetadataManager.test.ts} +10 -8
- package/src/{packetTrailer/PacketTrailerManager.ts → frameMetadata/FrameMetadataManager.ts} +21 -15
- package/src/{packetTrailer/packetTrailer.test.ts → frameMetadata/frameMetadata.test.ts} +1 -1
- package/src/{packetTrailer/packetTrailer.ts → frameMetadata/frameMetadata.ts} +9 -9
- package/src/{packetTrailer → frameMetadata}/types.ts +12 -6
- package/src/{packetTrailer → frameMetadata}/utils.test.ts +19 -19
- package/src/{packetTrailer → frameMetadata}/utils.ts +11 -11
- package/src/{packetTrailer/worker/packetTrailer.worker.ts → frameMetadata/worker/frameMetadata.worker.ts} +5 -5
- package/src/{packetTrailer → frameMetadata}/worker/tsconfig.json +4 -1
- package/src/index.ts +13 -2
- package/src/options.ts +9 -4
- package/src/room/PCTransport.ts +4 -0
- package/src/room/PCTransportManager.ts +4 -0
- package/src/room/RTCEngine.test.ts +143 -9
- package/src/room/RTCEngine.ts +111 -31
- package/src/room/Room.ts +10 -10
- package/src/room/participant/LocalParticipant.test.ts +19 -19
- package/src/room/participant/LocalParticipant.ts +21 -16
- package/src/room/participant/publishUtils.test.ts +6 -0
- package/src/room/participant/publishUtils.ts +4 -1
- package/src/room/rpc/client/RpcClientManager.ts +1 -1
- package/src/room/rpc/server/RpcServerManager.ts +1 -1
- package/src/room/token-source/TokenSource.ts +9 -23
- package/src/room/token-source/types.ts +1 -0
- package/src/room/token-source/utils.ts +1 -0
- package/src/room/track/{PacketTrailerExtractor.ts → FrameMetadataExtractor.ts} +8 -8
- package/src/room/track/RemoteVideoTrack.ts +7 -11
- package/src/room/track/options.ts +19 -5
- package/dist/livekit-client.pt.worker.js +0 -2
- package/dist/livekit-client.pt.worker.mjs.map +0 -1
- package/dist/src/packetTrailer/packetTrailer.d.ts.map +0 -1
- package/dist/src/packetTrailer/types.d.ts.map +0 -1
- package/dist/src/packetTrailer/utils.d.ts +0 -9
- package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts +0 -2
- package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts.map +0 -1
- package/dist/src/room/track/PacketTrailerExtractor.d.ts +0 -19
- package/dist/src/room/track/PacketTrailerExtractor.d.ts.map +0 -1
- package/dist/ts4.2/packetTrailer/utils.d.ts +0 -9
- package/dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts +0 -2
- package/dist/ts4.2/room/track/PacketTrailerExtractor.d.ts +0 -19
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FrameMetadata } from '../../frameMetadata/types';
|
|
2
2
|
import type { VideoReceiverStats } from '../stats';
|
|
3
3
|
import type { LoggerOptions } from '../types';
|
|
4
|
-
import type {
|
|
4
|
+
import type { FrameMetadataExtractor } from './FrameMetadataExtractor';
|
|
5
5
|
import RemoteTrack from './RemoteTrack';
|
|
6
6
|
import { Track } from './Track';
|
|
7
7
|
import type { AdaptiveStreamSettings } from './types';
|
|
@@ -12,7 +12,7 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
|
|
|
12
12
|
private lastVisible?;
|
|
13
13
|
private lastDimensions?;
|
|
14
14
|
/** @internal */
|
|
15
|
-
|
|
15
|
+
frameMetadataExtractor?: FrameMetadataExtractor;
|
|
16
16
|
constructor(mediaTrack: MediaStreamTrack, sid: string, receiver: RTCRtpReceiver, adaptiveStreamSettings?: AdaptiveStreamSettings, loggerOptions?: LoggerOptions);
|
|
17
17
|
get isAdaptiveStream(): boolean;
|
|
18
18
|
/**
|
|
@@ -20,13 +20,13 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
|
|
|
20
20
|
* Use with the `TrackEvent.TimeSyncUpdate` event to correlate displayed frames
|
|
21
21
|
* with their capture-time metadata.
|
|
22
22
|
*
|
|
23
|
-
* Requires the room to be configured with the `
|
|
24
|
-
* and the publishing track to have
|
|
23
|
+
* Requires the room to be configured with the `frameMetadata` worker option
|
|
24
|
+
* and the publishing track to have frame metadata features enabled.
|
|
25
25
|
*
|
|
26
26
|
*/
|
|
27
|
-
lookupFrameMetadata({ rtpTimestamp
|
|
27
|
+
lookupFrameMetadata({ rtpTimestamp }: {
|
|
28
28
|
rtpTimestamp: number;
|
|
29
|
-
}):
|
|
29
|
+
}): FrameMetadata | undefined;
|
|
30
30
|
setStreamState(value: Track.StreamState): void;
|
|
31
31
|
/**
|
|
32
32
|
* Note: When using adaptiveStream, you need to use remoteVideoTrack.attach() to add the track to a HTMLVideoElement, otherwise your video tracks might never start
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FrameMetadataPublishOptions } from '../../frameMetadata/types';
|
|
2
2
|
import type { Track } from './Track';
|
|
3
3
|
import type { AudioProcessorOptions, TrackProcessor, VideoProcessorOptions } from './processor/types';
|
|
4
4
|
export interface TrackPublishDefaults {
|
|
@@ -113,14 +113,18 @@ export interface TrackPublishDefaults {
|
|
|
113
113
|
*/
|
|
114
114
|
preConnectBuffer?: boolean;
|
|
115
115
|
/**
|
|
116
|
-
*
|
|
116
|
+
* Frame metadata to append to published video frames.
|
|
117
117
|
*
|
|
118
|
-
* Requires either room-level
|
|
119
|
-
* because encoded frame transforms are used to write the
|
|
118
|
+
* Requires either room-level frame metadata worker configuration or E2EE,
|
|
119
|
+
* because encoded frame transforms are used to write the metadata.
|
|
120
120
|
*
|
|
121
121
|
* @experimental
|
|
122
122
|
*/
|
|
123
|
-
|
|
123
|
+
frameMetadata?: FrameMetadataPublishOptions;
|
|
124
|
+
/**
|
|
125
|
+
* @deprecated Use {@link TrackPublishDefaults.frameMetadata} instead.
|
|
126
|
+
*/
|
|
127
|
+
packetTrailer?: FrameMetadataPublishOptions;
|
|
124
128
|
}
|
|
125
129
|
/**
|
|
126
130
|
* Options when publishing tracks
|
|
@@ -248,6 +252,14 @@ export interface AudioCaptureOptions {
|
|
|
248
252
|
* @see https://w3c.github.io/mediacapture-extensions/#voiceisolation-constraint
|
|
249
253
|
*/
|
|
250
254
|
voiceIsolation?: ConstrainBoolean;
|
|
255
|
+
/**
|
|
256
|
+
* @experimental
|
|
257
|
+
* when capturing system/screen-share audio, excludes the local participant's own audio
|
|
258
|
+
* from the captured stream to prevent echo. Browser support is not widespread yet
|
|
259
|
+
* (as of mid 2026, Chromium-based browsers only).
|
|
260
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/restrictOwnAudio
|
|
261
|
+
*/
|
|
262
|
+
restrictOwnAudio?: ConstrainBoolean;
|
|
251
263
|
/**
|
|
252
264
|
* the sample rate or range of sample rates which are acceptable and/or required.
|
|
253
265
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livekit-client",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.20.0",
|
|
4
4
|
"description": "JavaScript/TypeScript client SDK for LiveKit",
|
|
5
5
|
"main": "./dist/livekit-client.umd.js",
|
|
6
6
|
"unpkg": "./dist/livekit-client.umd.js",
|
|
@@ -17,9 +17,14 @@
|
|
|
17
17
|
"require": "./dist/livekit-client.e2ee.worker.js"
|
|
18
18
|
},
|
|
19
19
|
"./packet-trailer-worker": {
|
|
20
|
-
"types": "./dist/src/
|
|
21
|
-
"import": "./dist/livekit-client.
|
|
22
|
-
"require": "./dist/livekit-client.
|
|
20
|
+
"types": "./dist/src/frameMetadata/worker/frameMetadata.worker.d.ts",
|
|
21
|
+
"import": "./dist/livekit-client.fm.worker.mjs",
|
|
22
|
+
"require": "./dist/livekit-client.fm.worker.js"
|
|
23
|
+
},
|
|
24
|
+
"./frame-metadata-worker": {
|
|
25
|
+
"types": "./dist/src/frameMetadata/worker/frameMetadata.worker.d.ts",
|
|
26
|
+
"import": "./dist/livekit-client.fm.worker.mjs",
|
|
27
|
+
"require": "./dist/livekit-client.fm.worker.js"
|
|
23
28
|
}
|
|
24
29
|
},
|
|
25
30
|
"files": [
|
|
@@ -35,8 +40,8 @@
|
|
|
35
40
|
"./dist/src/e2ee/worker/e2ee.worker.d.ts": [
|
|
36
41
|
"./dist/ts4.2/e2ee/worker/e2ee.worker.d.ts"
|
|
37
42
|
],
|
|
38
|
-
"./dist/src/
|
|
39
|
-
"./dist/ts4.2/
|
|
43
|
+
"./dist/src/frameMetadata/worker/frameMetadata.worker.d.ts": [
|
|
44
|
+
"./dist/ts4.2/frameMetadata/worker/frameMetadata.worker.d.ts"
|
|
40
45
|
]
|
|
41
46
|
}
|
|
42
47
|
},
|
|
@@ -45,7 +50,7 @@
|
|
|
45
50
|
"license": "Apache-2.0",
|
|
46
51
|
"dependencies": {
|
|
47
52
|
"@livekit/mutex": "1.1.1",
|
|
48
|
-
"@livekit/protocol": "1.
|
|
53
|
+
"@livekit/protocol": "1.46.6",
|
|
49
54
|
"events": "^3.3.0",
|
|
50
55
|
"jose": "^6.1.0",
|
|
51
56
|
"loglevel": "^1.9.2",
|
|
@@ -58,8 +63,8 @@
|
|
|
58
63
|
"@types/dom-mediacapture-record": "^1"
|
|
59
64
|
},
|
|
60
65
|
"devDependencies": {
|
|
61
|
-
"@babel/core": "7.29.
|
|
62
|
-
"@babel/preset-env": "7.29.
|
|
66
|
+
"@babel/core": "7.29.7",
|
|
67
|
+
"@babel/preset-env": "7.29.7",
|
|
63
68
|
"@bufbuild/protoc-gen-es": "^1.10.0",
|
|
64
69
|
"@changesets/cli": "2.31.0",
|
|
65
70
|
"@eslint/js": "10.0.1",
|
|
@@ -69,16 +74,16 @@
|
|
|
69
74
|
"@rollup/plugin-commonjs": "29.0.2",
|
|
70
75
|
"@rollup/plugin-json": "6.1.0",
|
|
71
76
|
"@rollup/plugin-node-resolve": "16.0.3",
|
|
72
|
-
"@rollup/plugin-terser": "^0.
|
|
73
|
-
"@size-limit/file": "^
|
|
74
|
-
"@size-limit/webpack": "^
|
|
77
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
78
|
+
"@size-limit/file": "^12.1.0",
|
|
79
|
+
"@size-limit/webpack": "^12.1.0",
|
|
75
80
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
76
|
-
"@trivago/prettier-plugin-sort-imports": "^
|
|
81
|
+
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
77
82
|
"@types/events": "^3.0.3",
|
|
78
83
|
"@types/sdp-transform": "2.15.0",
|
|
79
84
|
"@types/ua-parser-js": "0.7.39",
|
|
80
|
-
"@typescript-eslint/eslint-plugin": "8.59.
|
|
81
|
-
"@typescript-eslint/parser": "8.59.
|
|
85
|
+
"@typescript-eslint/eslint-plugin": "8.59.4",
|
|
86
|
+
"@typescript-eslint/parser": "8.59.4",
|
|
82
87
|
"downlevel-dts": "^0.11.0",
|
|
83
88
|
"eslint": "10.4.0",
|
|
84
89
|
"eslint-config-airbnb-extended": "^2.3.2",
|
|
@@ -101,11 +106,11 @@
|
|
|
101
106
|
"typedoc-plugin-no-inherit": "1.6.1",
|
|
102
107
|
"typescript": "5.9.3",
|
|
103
108
|
"typescript-eslint": "^8.47.0",
|
|
104
|
-
"vite": "7.3.
|
|
105
|
-
"vitest": "^
|
|
109
|
+
"vite": "7.3.5",
|
|
110
|
+
"vitest": "^4.0.0"
|
|
106
111
|
},
|
|
107
112
|
"scripts": {
|
|
108
|
-
"build": "rollup --config --bundleConfigAsCjs && rollup --config rollup.config.worker.js --bundleConfigAsCjs && rollup --config rollup.config.
|
|
113
|
+
"build": "rollup --config --bundleConfigAsCjs && rollup --config rollup.config.worker.js --bundleConfigAsCjs && rollup --config rollup.config.fm-worker.js --bundleConfigAsCjs && pnpm downlevel-dts",
|
|
109
114
|
"build:clean": "rm -rf ./dist && pnpm build",
|
|
110
115
|
"build:watch": "rollup --watch --config --bundleConfigAsCjs",
|
|
111
116
|
"build:worker:watch": "rollup --watch --config rollup.config.worker.js --bundleConfigAsCjs",
|
|
@@ -77,7 +77,7 @@ function mockWebSocketStream(options: MockWebSocketStreamOptions = {}) {
|
|
|
77
77
|
onUrl,
|
|
78
78
|
} = options;
|
|
79
79
|
|
|
80
|
-
return vi.mocked(WebSocketStream).mockImplementationOnce((url)
|
|
80
|
+
return vi.mocked(WebSocketStream).mockImplementationOnce(function (url) {
|
|
81
81
|
onUrl?.(url);
|
|
82
82
|
return {
|
|
83
83
|
url: 'wss://test.livekit.io',
|
|
@@ -283,7 +283,7 @@ describe('SignalClient.connect', () => {
|
|
|
283
283
|
it('should reject when AbortSignal is triggered', async () => {
|
|
284
284
|
const abortController = new AbortController();
|
|
285
285
|
|
|
286
|
-
vi.mocked(WebSocketStream).mockImplementation(()
|
|
286
|
+
vi.mocked(WebSocketStream).mockImplementation(function () {
|
|
287
287
|
// Simulate abort
|
|
288
288
|
setTimeout(() => abortController.abort(new Error('User aborted connection')), 50);
|
|
289
289
|
|
|
@@ -344,7 +344,7 @@ describe('SignalClient.connect', () => {
|
|
|
344
344
|
extensions: '',
|
|
345
345
|
};
|
|
346
346
|
|
|
347
|
-
vi.mocked(WebSocketStream).mockImplementation(()
|
|
347
|
+
vi.mocked(WebSocketStream).mockImplementation(function () {
|
|
348
348
|
return {
|
|
349
349
|
url: 'wss://test.livekit.io',
|
|
350
350
|
opened: Promise.resolve(mockConnection),
|
|
@@ -526,7 +526,7 @@ describe('SignalClient.connect', () => {
|
|
|
526
526
|
closedResolve = resolve;
|
|
527
527
|
});
|
|
528
528
|
|
|
529
|
-
vi.mocked(WebSocketStream).mockImplementation(()
|
|
529
|
+
vi.mocked(WebSocketStream).mockImplementation(function () {
|
|
530
530
|
// Simulate close during connection
|
|
531
531
|
queueMicrotask(() => {
|
|
532
532
|
closedResolve({ closeCode: 1006, reason: 'Connection lost' });
|
package/src/api/SignalClient.ts
CHANGED
|
@@ -376,13 +376,17 @@ export class SignalClient {
|
|
|
376
376
|
if (redactedUrl.searchParams.has('access_token')) {
|
|
377
377
|
redactedUrl.searchParams.set('access_token', '<redacted>');
|
|
378
378
|
}
|
|
379
|
+
|
|
380
|
+
if (this.ws) {
|
|
381
|
+
const startClose = performance.now();
|
|
382
|
+
await this.close(false);
|
|
383
|
+
this.log.debug(`closed previous ws connection in ${performance.now() - startClose}ms`);
|
|
384
|
+
}
|
|
385
|
+
|
|
379
386
|
this.log.info(`signal connecting to ${redactedUrl}`, {
|
|
380
387
|
reconnect: opts.reconnect,
|
|
381
388
|
reconnectReason: opts.reconnectReason,
|
|
382
389
|
});
|
|
383
|
-
if (this.ws) {
|
|
384
|
-
await this.close(false);
|
|
385
|
-
}
|
|
386
390
|
this.ws = new WebSocketStream<ArrayBuffer>(rtcUrl);
|
|
387
391
|
|
|
388
392
|
try {
|
|
@@ -129,24 +129,24 @@ describe('WebSocketStream', () => {
|
|
|
129
129
|
vi.clearAllMocks();
|
|
130
130
|
|
|
131
131
|
// Store original WebSocket
|
|
132
|
-
originalWebSocket =
|
|
132
|
+
originalWebSocket = globalThis.WebSocket;
|
|
133
133
|
|
|
134
134
|
// Mock WebSocket globally
|
|
135
|
-
|
|
135
|
+
globalThis.WebSocket = vi.fn(function (url: string, protocols?: string | string[]) {
|
|
136
136
|
mockWebSocket = new MockWebSocket(url, protocols);
|
|
137
137
|
return mockWebSocket as any;
|
|
138
138
|
}) as any;
|
|
139
139
|
|
|
140
140
|
// Add constants to the mocked WebSocket
|
|
141
|
-
(
|
|
142
|
-
(
|
|
143
|
-
(
|
|
144
|
-
(
|
|
141
|
+
(globalThis.WebSocket as any).CONNECTING = MockWebSocket.CONNECTING;
|
|
142
|
+
(globalThis.WebSocket as any).OPEN = MockWebSocket.OPEN;
|
|
143
|
+
(globalThis.WebSocket as any).CLOSING = MockWebSocket.CLOSING;
|
|
144
|
+
(globalThis.WebSocket as any).CLOSED = MockWebSocket.CLOSED;
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
afterEach(() => {
|
|
148
148
|
// Restore original WebSocket
|
|
149
|
-
|
|
149
|
+
globalThis.WebSocket = originalWebSocket;
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
describe('Constructor and Initialization', () => {
|
package/src/e2ee/E2eeManager.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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 { hasFrameMetadataPublishOptions } from '../frameMetadata/utils';
|
|
4
5
|
import log, { LogLevel, workerLogger } from '../logger';
|
|
5
|
-
import { hasPacketTrailerPublishOptions } from '../packetTrailer/utils';
|
|
6
6
|
import type RTCEngine from '../room/RTCEngine';
|
|
7
7
|
import type Room from '../room/Room';
|
|
8
8
|
import { ConnectionState } from '../room/Room';
|
|
@@ -230,7 +230,7 @@ export class E2EEManager
|
|
|
230
230
|
}
|
|
231
231
|
break;
|
|
232
232
|
case 'packetTrailerMetadata':
|
|
233
|
-
this.
|
|
233
|
+
this.handleFrameMetadata(data.trackId, data.rtpTimestamp, data.ssrc, data.metadata);
|
|
234
234
|
break;
|
|
235
235
|
default:
|
|
236
236
|
break;
|
|
@@ -242,7 +242,7 @@ export class E2EEManager
|
|
|
242
242
|
this.emit(EncryptionEvent.EncryptionError, ev.error, undefined);
|
|
243
243
|
};
|
|
244
244
|
|
|
245
|
-
private
|
|
245
|
+
private handleFrameMetadata(
|
|
246
246
|
trackId: string,
|
|
247
247
|
rtpTimestamp: number,
|
|
248
248
|
ssrc: number,
|
|
@@ -260,9 +260,9 @@ export class E2EEManager
|
|
|
260
260
|
pub.track &&
|
|
261
261
|
pub.track.mediaStreamID === trackId &&
|
|
262
262
|
pub.track instanceof RemoteVideoTrack &&
|
|
263
|
-
pub.track.
|
|
263
|
+
pub.track.frameMetadataExtractor
|
|
264
264
|
) {
|
|
265
|
-
pub.track.
|
|
265
|
+
pub.track.frameMetadataExtractor.storeMetadata(rtpTimestamp, ssrc, metadata);
|
|
266
266
|
return;
|
|
267
267
|
}
|
|
268
268
|
}
|
|
@@ -509,7 +509,9 @@ export class E2EEManager
|
|
|
509
509
|
sender,
|
|
510
510
|
track.mediaStreamID,
|
|
511
511
|
undefined,
|
|
512
|
-
isVideoTrack(track)
|
|
512
|
+
isVideoTrack(track)
|
|
513
|
+
? (track.publishOptions?.frameMetadata ?? track.publishOptions?.packetTrailer)
|
|
514
|
+
: undefined,
|
|
513
515
|
);
|
|
514
516
|
}
|
|
515
517
|
|
|
@@ -598,7 +600,7 @@ export class E2EEManager
|
|
|
598
600
|
sender: RTCRtpSender,
|
|
599
601
|
trackId: string,
|
|
600
602
|
codec?: VideoCodec,
|
|
601
|
-
|
|
603
|
+
frameMetadata?: TrackPublishOptions['frameMetadata'],
|
|
602
604
|
) {
|
|
603
605
|
if (E2EE_FLAG in sender || !this.worker) {
|
|
604
606
|
return;
|
|
@@ -615,8 +617,8 @@ export class E2EEManager
|
|
|
615
617
|
participantIdentity: this.room.localParticipant.identity,
|
|
616
618
|
trackId,
|
|
617
619
|
codec,
|
|
618
|
-
hasPacketTrailer:
|
|
619
|
-
packetTrailer,
|
|
620
|
+
hasPacketTrailer: hasFrameMetadataPublishOptions(frameMetadata),
|
|
621
|
+
packetTrailer: frameMetadata,
|
|
620
622
|
};
|
|
621
623
|
// @ts-ignore
|
|
622
624
|
sender.transform = new RTCRtpScriptTransform(this.worker, options);
|
|
@@ -633,8 +635,8 @@ export class E2EEManager
|
|
|
633
635
|
trackId,
|
|
634
636
|
participantIdentity: this.room.localParticipant.identity,
|
|
635
637
|
isReuse: false,
|
|
636
|
-
hasPacketTrailer:
|
|
637
|
-
packetTrailer,
|
|
638
|
+
hasPacketTrailer: hasFrameMetadataPublishOptions(frameMetadata),
|
|
639
|
+
packetTrailer: frameMetadata,
|
|
638
640
|
},
|
|
639
641
|
};
|
|
640
642
|
this.worker.postMessage(msg, [senderStreams.readable, senderStreams.writable]);
|
package/src/e2ee/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { FrameMetadataPayload } from '../frameMetadata/frameMetadata';
|
|
2
|
+
import type { FrameMetadataPublishOptions } from '../frameMetadata/types';
|
|
1
3
|
import type { LogLevel } from '../logger';
|
|
2
|
-
import type { PacketTrailerFramePayload } from '../packetTrailer/packetTrailer';
|
|
3
|
-
import type { PacketTrailerPublishOptions } from '../packetTrailer/types';
|
|
4
4
|
import type { VideoCodec } from '../room/track/options';
|
|
5
5
|
import type { BaseE2EEManager } from './E2eeManager';
|
|
6
6
|
import type { BaseKeyProvider } from './KeyProvider';
|
|
@@ -62,7 +62,7 @@ export interface EncodeMessage extends BaseMessage {
|
|
|
62
62
|
/**
|
|
63
63
|
* Packet trailer metadata to append on published video frames.
|
|
64
64
|
*/
|
|
65
|
-
packetTrailer?:
|
|
65
|
+
packetTrailer?: FrameMetadataPublishOptions;
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -165,7 +165,7 @@ export interface EncryptDataResponseMessage extends BaseMessage {
|
|
|
165
165
|
|
|
166
166
|
export interface PTMetadataFromE2EEMessage extends BaseMessage {
|
|
167
167
|
kind: 'packetTrailerMetadata';
|
|
168
|
-
data:
|
|
168
|
+
data: FrameMetadataPayload;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
export type E2EEWorkerMessage =
|
|
@@ -249,5 +249,5 @@ export type ScriptTransformOptions = {
|
|
|
249
249
|
/**
|
|
250
250
|
* Packet trailer metadata to append on published video frames.
|
|
251
251
|
*/
|
|
252
|
-
packetTrailer?:
|
|
252
|
+
packetTrailer?: FrameMetadataPublishOptions;
|
|
253
253
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vitest } from 'vitest';
|
|
2
|
-
import { appendPacketTrailer, extractPacketTrailer } from '../../
|
|
3
|
-
import type {
|
|
2
|
+
import { appendPacketTrailer, extractPacketTrailer } from '../../frameMetadata/frameMetadata';
|
|
3
|
+
import type { FrameMetadataPublishOptions } from '../../frameMetadata/types';
|
|
4
4
|
import { IV_LENGTH, KEY_PROVIDER_DEFAULTS } from '../constants';
|
|
5
5
|
import { CryptorEvent } from '../events';
|
|
6
6
|
import type { KeyProviderOptions } from '../types';
|
|
@@ -69,7 +69,7 @@ function prepareParticipantTestDecoder(
|
|
|
69
69
|
function prepareParticipantTestEncoder(
|
|
70
70
|
participantIdentity: string,
|
|
71
71
|
partialKeyProviderOptions: Partial<KeyProviderOptions>,
|
|
72
|
-
packetTrailer?:
|
|
72
|
+
packetTrailer?: FrameMetadataPublishOptions,
|
|
73
73
|
) {
|
|
74
74
|
return prepareParticipantTest(
|
|
75
75
|
'encode',
|
|
@@ -83,7 +83,7 @@ function prepareParticipantTest(
|
|
|
83
83
|
mode: 'encode' | 'decode',
|
|
84
84
|
participantIdentity: string,
|
|
85
85
|
partialKeyProviderOptions: Partial<KeyProviderOptions>,
|
|
86
|
-
packetTrailer?:
|
|
86
|
+
packetTrailer?: FrameMetadataPublishOptions,
|
|
87
87
|
): {
|
|
88
88
|
keys: ParticipantKeyHandler;
|
|
89
89
|
cryptor: FrameCryptor;
|
|
@@ -536,7 +536,7 @@ describe('FrameCryptor', () => {
|
|
|
536
536
|
const postMessage = vitest.fn();
|
|
537
537
|
vitest.stubGlobal('postMessage', postMessage);
|
|
538
538
|
encryptionEnabledMap.set(participantIdentity, false);
|
|
539
|
-
cryptor.
|
|
539
|
+
cryptor.setHasFrameMetadata(true);
|
|
540
540
|
|
|
541
541
|
const payload = Uint8Array.from([1, 2, 3, 4]);
|
|
542
542
|
const trailer = appendPacketTrailer(payload, 1_744_249_600_123_456n, 0);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// TODO code inspired by https://github.com/webrtc/samples/blob/gh-pages/src/content/insertable-streams/endtoend-encryption/js/worker.js
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
import type TypedEventEmitter from 'typed-emitter';
|
|
4
|
-
import { workerLogger } from '../../logger';
|
|
5
4
|
import {
|
|
6
5
|
appendPacketTrailerToEncodedFrame,
|
|
7
6
|
processPacketTrailer,
|
|
8
|
-
} from '../../
|
|
9
|
-
import type {
|
|
10
|
-
import {
|
|
7
|
+
} from '../../frameMetadata/frameMetadata';
|
|
8
|
+
import type { FrameMetadataPublishOptions } from '../../frameMetadata/types';
|
|
9
|
+
import { hasFrameMetadataPublishOptions } from '../../frameMetadata/utils';
|
|
10
|
+
import { workerLogger } from '../../logger';
|
|
11
11
|
import { type VideoCodec, videoCodecs } from '../../room/track/options';
|
|
12
12
|
import { mimeTypeToVideoCodecString } from '../../room/track/utils';
|
|
13
13
|
import { ENCRYPTION_ALGORITHM, IV_LENGTH, UNENCRYPTED_BYTES } from '../constants';
|
|
@@ -88,11 +88,11 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
88
88
|
* When false, we skip the per-frame trailer extraction path entirely
|
|
89
89
|
* on decode to avoid unnecessary work on tracks that don't use it.
|
|
90
90
|
*/
|
|
91
|
-
private
|
|
91
|
+
private hasFrameMetadata: boolean = false;
|
|
92
92
|
|
|
93
|
-
private
|
|
93
|
+
private frameMetadataOpts?: FrameMetadataPublishOptions;
|
|
94
94
|
|
|
95
|
-
private
|
|
95
|
+
private frameMetadataFrameId = 0;
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* Throttling mechanism for decryption errors to prevent memory leaks
|
|
@@ -212,13 +212,13 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
212
212
|
* trailer data. When false, {@link decodeFunction} skips the per-frame
|
|
213
213
|
* trailer extraction branch entirely.
|
|
214
214
|
*/
|
|
215
|
-
|
|
216
|
-
this.
|
|
215
|
+
setHasFrameMetadata(hasFrameMetadata: boolean) {
|
|
216
|
+
this.hasFrameMetadata = hasFrameMetadata;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
|
|
220
|
-
this.
|
|
221
|
-
this.
|
|
219
|
+
setFrameMetadataOpts(frameMetadata?: FrameMetadataPublishOptions) {
|
|
220
|
+
this.frameMetadataOpts = frameMetadata;
|
|
221
|
+
this.frameMetadataFrameId = 0;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
setupTransform(
|
|
@@ -228,14 +228,14 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
228
228
|
trackId: string,
|
|
229
229
|
isReuse: boolean,
|
|
230
230
|
codec?: VideoCodec,
|
|
231
|
-
|
|
231
|
+
frameMetadata?: FrameMetadataPublishOptions,
|
|
232
232
|
) {
|
|
233
233
|
if (codec) {
|
|
234
234
|
workerLogger.info('setting codec on cryptor to', { codec });
|
|
235
235
|
this.videoCodec = codec;
|
|
236
236
|
}
|
|
237
237
|
if (operation === 'encode') {
|
|
238
|
-
this.
|
|
238
|
+
this.setFrameMetadataOpts(frameMetadata);
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
workerLogger.debug('Setting up frame cryptor transform', {
|
|
@@ -406,7 +406,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
if (!this.isEnabled()) {
|
|
409
|
-
this.
|
|
409
|
+
this.appendFrameMetadata(encodedFrame);
|
|
410
410
|
return controller.enqueue(encodedFrame);
|
|
411
411
|
}
|
|
412
412
|
const keySet = this.keys.getKeySet();
|
|
@@ -475,7 +475,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
475
475
|
newData.set(newDataWithoutHeader, frameHeader.byteLength);
|
|
476
476
|
|
|
477
477
|
encodedFrame.data = newData.buffer;
|
|
478
|
-
this.
|
|
478
|
+
this.appendFrameMetadata(encodedFrame);
|
|
479
479
|
|
|
480
480
|
return controller.enqueue(encodedFrame);
|
|
481
481
|
} catch (e: any) {
|
|
@@ -494,16 +494,20 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
494
494
|
}
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
-
private
|
|
498
|
-
if (!
|
|
497
|
+
private appendFrameMetadata(encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame) {
|
|
498
|
+
if (!hasFrameMetadataPublishOptions(this.frameMetadataOpts) || !isVideoFrame(encodedFrame)) {
|
|
499
499
|
return;
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
-
if (this.
|
|
503
|
-
this.
|
|
504
|
-
this.
|
|
502
|
+
if (this.frameMetadataOpts?.frameId) {
|
|
503
|
+
this.frameMetadataFrameId =
|
|
504
|
+
this.frameMetadataFrameId === 0xffffffff ? 1 : this.frameMetadataFrameId + 1;
|
|
505
505
|
}
|
|
506
|
-
appendPacketTrailerToEncodedFrame(
|
|
506
|
+
appendPacketTrailerToEncodedFrame(
|
|
507
|
+
encodedFrame,
|
|
508
|
+
this.frameMetadataOpts,
|
|
509
|
+
this.frameMetadataFrameId,
|
|
510
|
+
);
|
|
507
511
|
}
|
|
508
512
|
|
|
509
513
|
/**
|
|
@@ -516,7 +520,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
516
520
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
|
517
521
|
controller: TransformStreamDefaultController,
|
|
518
522
|
) {
|
|
519
|
-
if (this.
|
|
523
|
+
if (this.hasFrameMetadata && isVideoFrame(encodedFrame)) {
|
|
520
524
|
try {
|
|
521
525
|
const ptResult = processPacketTrailer(encodedFrame, this.trackId);
|
|
522
526
|
if (ptResult.data) {
|
|
@@ -64,7 +64,7 @@ onmessage = (ev) => {
|
|
|
64
64
|
break;
|
|
65
65
|
case 'decode':
|
|
66
66
|
let cryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
|
67
|
-
cryptor.
|
|
67
|
+
cryptor.setHasFrameMetadata(data.hasPacketTrailer);
|
|
68
68
|
cryptor.setupTransform(
|
|
69
69
|
kind,
|
|
70
70
|
data.readableStream,
|
|
@@ -76,7 +76,7 @@ onmessage = (ev) => {
|
|
|
76
76
|
break;
|
|
77
77
|
case 'encode':
|
|
78
78
|
let pubCryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
|
79
|
-
pubCryptor.
|
|
79
|
+
pubCryptor.setHasFrameMetadata(data.hasPacketTrailer);
|
|
80
80
|
pubCryptor.setupTransform(
|
|
81
81
|
kind,
|
|
82
82
|
data.readableStream,
|
|
@@ -84,7 +84,7 @@ onmessage = (ev) => {
|
|
|
84
84
|
data.trackId,
|
|
85
85
|
data.isReuse,
|
|
86
86
|
data.codec,
|
|
87
|
-
data.packetTrailer,
|
|
87
|
+
data.packetTrailer, // wire format still uses 'packetTrailer' field name
|
|
88
88
|
);
|
|
89
89
|
break;
|
|
90
90
|
|
|
@@ -164,7 +164,7 @@ onmessage = (ev) => {
|
|
|
164
164
|
case 'updateCodec':
|
|
165
165
|
const trackCryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
|
166
166
|
trackCryptor.setVideoCodec(data.codec);
|
|
167
|
-
trackCryptor.
|
|
167
|
+
trackCryptor.setHasFrameMetadata(data.hasPacketTrailer);
|
|
168
168
|
workerLogger.info('updated codec', {
|
|
169
169
|
participantIdentity: data.participantIdentity,
|
|
170
170
|
trackId: data.trackId,
|
|
@@ -343,7 +343,7 @@ if (self.RTCTransformEvent) {
|
|
|
343
343
|
const { kind, participantIdentity, trackId, codec, hasPacketTrailer } = options;
|
|
344
344
|
messageQueue.run(async () => {
|
|
345
345
|
const cryptor = getTrackCryptor(participantIdentity, trackId);
|
|
346
|
-
cryptor.
|
|
346
|
+
cryptor.setHasFrameMetadata(hasPacketTrailer);
|
|
347
347
|
workerLogger.debug('onrtctransform setup', { participantIdentity, trackId, codec });
|
|
348
348
|
cryptor.setupTransform(
|
|
349
349
|
kind,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import type { TrackInfo } from '@livekit/protocol';
|
|
3
|
-
import {
|
|
3
|
+
import { FrameMetadataManager } from './FrameMetadataManager';
|
|
4
4
|
|
|
5
|
-
describe('
|
|
5
|
+
describe('FrameMetadataManager', () => {
|
|
6
6
|
const originalRTCRtpSender = window.RTCRtpSender;
|
|
7
7
|
const originalUserAgent = navigator.userAgent;
|
|
8
8
|
const originalRTCRtpScriptTransform = (window as unknown as { RTCRtpScriptTransform?: unknown })
|
|
@@ -63,7 +63,7 @@ describe('PacketTrailerManager', () => {
|
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
function setupWorkerReceiver(manager:
|
|
66
|
+
function setupWorkerReceiver(manager: FrameMetadataManager, receiver: RTCRtpReceiver) {
|
|
67
67
|
(
|
|
68
68
|
manager as unknown as {
|
|
69
69
|
setupWorkerReceiver: (receiver: RTCRtpReceiver, newTrackId: string) => void;
|
|
@@ -72,7 +72,7 @@ describe('PacketTrailerManager', () => {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function setupReceiver(
|
|
75
|
-
manager:
|
|
75
|
+
manager: FrameMetadataManager,
|
|
76
76
|
receiver: RTCRtpReceiver,
|
|
77
77
|
trackId: string,
|
|
78
78
|
trackInfo?: TrackInfo,
|
|
@@ -103,11 +103,13 @@ describe('PacketTrailerManager', () => {
|
|
|
103
103
|
it('uses RTCRtpScriptTransform for packet trailer extraction when supported', () => {
|
|
104
104
|
useSafariUserAgent();
|
|
105
105
|
const transform = {};
|
|
106
|
-
const RTCRtpScriptTransform = vi.fn(()
|
|
106
|
+
const RTCRtpScriptTransform = vi.fn(function () {
|
|
107
|
+
return transform;
|
|
108
|
+
});
|
|
107
109
|
setScriptTransform(RTCRtpScriptTransform);
|
|
108
110
|
|
|
109
111
|
const worker = {} as Worker;
|
|
110
|
-
const manager = new
|
|
112
|
+
const manager = new FrameMetadataManager({ worker });
|
|
111
113
|
const receiver = {
|
|
112
114
|
createEncodedStreams: vi.fn(),
|
|
113
115
|
} as unknown as RTCRtpReceiver;
|
|
@@ -129,7 +131,7 @@ describe('PacketTrailerManager', () => {
|
|
|
129
131
|
stubInsertableStreamsSupport();
|
|
130
132
|
|
|
131
133
|
const worker = { postMessage: vi.fn() } as unknown as Worker;
|
|
132
|
-
const manager = new
|
|
134
|
+
const manager = new FrameMetadataManager({ worker });
|
|
133
135
|
const { receiver, readable, writable, createEncodedStreams } = makeReceiver();
|
|
134
136
|
|
|
135
137
|
setupReceiver(manager, receiver, 'track-without-trailer');
|
|
@@ -153,7 +155,7 @@ describe('PacketTrailerManager', () => {
|
|
|
153
155
|
stubInsertableStreamsSupport();
|
|
154
156
|
|
|
155
157
|
const worker = { postMessage: vi.fn() } as unknown as Worker;
|
|
156
|
-
const manager = new
|
|
158
|
+
const manager = new FrameMetadataManager({ worker });
|
|
157
159
|
const { receiver } = makeReceiver();
|
|
158
160
|
const trackInfo = { packetTrailerFeatures: [1] } as unknown as TrackInfo;
|
|
159
161
|
|