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
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { appendPacketTrailerToEncodedFrame, processPacketTrailer } from '../packetTrailer';
|
|
2
|
+
import type {
|
|
3
|
+
PTMetadataMessage,
|
|
4
|
+
PTScriptTransformOptions,
|
|
5
|
+
PTWorkerMessage,
|
|
6
|
+
PacketTrailerPublishOptions,
|
|
7
|
+
} from '../types';
|
|
8
|
+
import { hasPacketTrailerPublishOptions } from '../utils';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Holds the trackId currently associated with a pipeline. A mutable
|
|
12
|
+
* wrapper is used so the transform closure always reads the latest
|
|
13
|
+
* trackId after a receiver gets re-bound to a new track.
|
|
14
|
+
*/
|
|
15
|
+
interface PipelineState {
|
|
16
|
+
trackId: string;
|
|
17
|
+
hasPacketTrailer: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const pipelines = new Map<string, PipelineState>();
|
|
21
|
+
|
|
22
|
+
onmessage = (ev: MessageEvent<PTWorkerMessage>) => {
|
|
23
|
+
const msg = ev.data;
|
|
24
|
+
|
|
25
|
+
switch (msg.kind) {
|
|
26
|
+
case 'init':
|
|
27
|
+
postMessage({ kind: 'initAck' });
|
|
28
|
+
break;
|
|
29
|
+
|
|
30
|
+
case 'decode':
|
|
31
|
+
setupDecodeTransform(
|
|
32
|
+
msg.data.readableStream,
|
|
33
|
+
msg.data.writableStream,
|
|
34
|
+
msg.data.trackId,
|
|
35
|
+
msg.data.hasPacketTrailer,
|
|
36
|
+
);
|
|
37
|
+
break;
|
|
38
|
+
|
|
39
|
+
case 'encode':
|
|
40
|
+
setupEncodeTransform(
|
|
41
|
+
msg.data.readableStream,
|
|
42
|
+
msg.data.writableStream,
|
|
43
|
+
msg.data.packetTrailer,
|
|
44
|
+
);
|
|
45
|
+
break;
|
|
46
|
+
|
|
47
|
+
case 'updateTrackId':
|
|
48
|
+
updateTrackId(msg.data.oldTrackId, msg.data.newTrackId, msg.data.hasPacketTrailer);
|
|
49
|
+
break;
|
|
50
|
+
|
|
51
|
+
default:
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function setupDecodeTransform(
|
|
57
|
+
readable: ReadableStream,
|
|
58
|
+
writable: WritableStream,
|
|
59
|
+
trackId: string,
|
|
60
|
+
hasPacketTrailer: boolean,
|
|
61
|
+
) {
|
|
62
|
+
const state: PipelineState = { trackId, hasPacketTrailer };
|
|
63
|
+
pipelines.set(trackId, state);
|
|
64
|
+
|
|
65
|
+
const transform = new TransformStream({
|
|
66
|
+
transform(
|
|
67
|
+
frame: RTCEncodedVideoFrame,
|
|
68
|
+
controller: TransformStreamDefaultController<RTCEncodedVideoFrame>,
|
|
69
|
+
) {
|
|
70
|
+
try {
|
|
71
|
+
if (state.hasPacketTrailer) {
|
|
72
|
+
const result = processPacketTrailer(frame, state.trackId);
|
|
73
|
+
if (result.data) {
|
|
74
|
+
frame.data = result.data;
|
|
75
|
+
}
|
|
76
|
+
if (result.payload) {
|
|
77
|
+
const msg: PTMetadataMessage = { kind: 'metadata', data: result.payload };
|
|
78
|
+
postMessage(msg);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Never drop frames on trailer-extraction failure — pass through so
|
|
83
|
+
// video keeps decoding even if metadata is lost for this frame.
|
|
84
|
+
}
|
|
85
|
+
controller.enqueue(frame);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
readable
|
|
90
|
+
.pipeThrough(transform)
|
|
91
|
+
.pipeTo(writable)
|
|
92
|
+
.catch(() => {
|
|
93
|
+
pipelines.delete(state.trackId);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function setupEncodeTransform(
|
|
98
|
+
readable: ReadableStream,
|
|
99
|
+
writable: WritableStream,
|
|
100
|
+
packetTrailer?: PacketTrailerPublishOptions,
|
|
101
|
+
) {
|
|
102
|
+
if (!hasPacketTrailerPublishOptions(packetTrailer)) {
|
|
103
|
+
readable.pipeTo(writable).catch(() => {});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let frameId = 0;
|
|
108
|
+
const transform = new TransformStream({
|
|
109
|
+
transform(
|
|
110
|
+
frame: RTCEncodedVideoFrame,
|
|
111
|
+
controller: TransformStreamDefaultController<RTCEncodedVideoFrame>,
|
|
112
|
+
) {
|
|
113
|
+
try {
|
|
114
|
+
if (packetTrailer?.frameId) {
|
|
115
|
+
frameId = frameId === 0xffffffff ? 1 : frameId + 1;
|
|
116
|
+
}
|
|
117
|
+
appendPacketTrailerToEncodedFrame(frame, packetTrailer, frameId);
|
|
118
|
+
} catch {
|
|
119
|
+
// Never drop frames on trailer-write failure.
|
|
120
|
+
}
|
|
121
|
+
controller.enqueue(frame);
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
readable
|
|
126
|
+
.pipeThrough(transform)
|
|
127
|
+
.pipeTo(writable)
|
|
128
|
+
.catch(() => {});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function updateTrackId(oldTrackId: string, newTrackId: string, hasPacketTrailer: boolean) {
|
|
132
|
+
const state = pipelines.get(oldTrackId);
|
|
133
|
+
if (state) {
|
|
134
|
+
state.trackId = newTrackId;
|
|
135
|
+
state.hasPacketTrailer = hasPacketTrailer;
|
|
136
|
+
pipelines.delete(oldTrackId);
|
|
137
|
+
pipelines.set(newTrackId, state);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Operations using RTCRtpScriptTransform.
|
|
142
|
+
// @ts-ignore
|
|
143
|
+
if (self.RTCTransformEvent) {
|
|
144
|
+
// @ts-ignore
|
|
145
|
+
self.onrtctransform = (event: RTCTransformEvent) => {
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
const transformer = event.transformer;
|
|
148
|
+
const options = transformer.options as PTScriptTransformOptions;
|
|
149
|
+
if (options.kind === 'encode') {
|
|
150
|
+
setupEncodeTransform(transformer.readable, transformer.writable, options.packetTrailer);
|
|
151
|
+
} else {
|
|
152
|
+
setupDecodeTransform(transformer.readable, transformer.writable, options.trackId, true);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -6,7 +6,7 @@ vi.mock('./utils', async () => {
|
|
|
6
6
|
const actual = await vi.importActual('./utils');
|
|
7
7
|
return {
|
|
8
8
|
...actual,
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
sleep: vi.fn((ms: number) => Promise.resolve()),
|
|
11
11
|
extractProjectFromUrl: vi.fn((url: URL) => {
|
|
12
12
|
// @ts-ignore
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import RTCEngine from './RTCEngine';
|
|
3
|
+
import { roomOptionDefaults } from './defaults';
|
|
4
|
+
|
|
5
|
+
describe('RTCEngine', () => {
|
|
6
|
+
const originalRTCRtpSender = window.RTCRtpSender;
|
|
7
|
+
const originalRTCRtpScriptTransform = (window as unknown as { RTCRtpScriptTransform?: unknown })
|
|
8
|
+
.RTCRtpScriptTransform;
|
|
9
|
+
const originalUserAgent = navigator.userAgent;
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
Object.defineProperty(window, 'RTCRtpSender', {
|
|
13
|
+
configurable: true,
|
|
14
|
+
value: originalRTCRtpSender,
|
|
15
|
+
writable: true,
|
|
16
|
+
});
|
|
17
|
+
Object.defineProperty(window, 'RTCRtpScriptTransform', {
|
|
18
|
+
configurable: true,
|
|
19
|
+
value: originalRTCRtpScriptTransform,
|
|
20
|
+
writable: true,
|
|
21
|
+
});
|
|
22
|
+
Object.defineProperty(window.navigator, 'userAgent', {
|
|
23
|
+
configurable: true,
|
|
24
|
+
value: originalUserAgent,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function stubInsertableStreamsSupport() {
|
|
29
|
+
class MockRTCRtpSender {
|
|
30
|
+
createEncodedStreams() {}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Object.defineProperty(window, 'RTCRtpSender', {
|
|
34
|
+
configurable: true,
|
|
35
|
+
value: MockRTCRtpSender,
|
|
36
|
+
writable: true,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function stubScriptTransformSupport() {
|
|
41
|
+
Object.defineProperty(window, 'RTCRtpScriptTransform', {
|
|
42
|
+
configurable: true,
|
|
43
|
+
value: class MockRTCRtpScriptTransform {},
|
|
44
|
+
writable: true,
|
|
45
|
+
});
|
|
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 makeRTCConfiguration(engine: RTCEngine) {
|
|
54
|
+
return (
|
|
55
|
+
engine as unknown as { makeRTCConfiguration: () => RTCConfiguration }
|
|
56
|
+
).makeRTCConfiguration();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function setupPacketTrailerSender(engine: RTCEngine, sender: RTCRtpSender, opts = {}) {
|
|
60
|
+
(
|
|
61
|
+
engine as unknown as {
|
|
62
|
+
setupPacketTrailerSender: (sender: RTCRtpSender, opts?: unknown) => void;
|
|
63
|
+
}
|
|
64
|
+
).setupPacketTrailerSender(sender, opts);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
it('does not enable encoded insertable streams without E2EE or a packet trailer worker', () => {
|
|
68
|
+
stubInsertableStreamsSupport();
|
|
69
|
+
|
|
70
|
+
const engine = new RTCEngine(roomOptionDefaults);
|
|
71
|
+
|
|
72
|
+
expect(makeRTCConfiguration(engine).encodedInsertableStreams).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('enables encoded insertable streams when a packet trailer worker is configured', () => {
|
|
76
|
+
stubInsertableStreamsSupport();
|
|
77
|
+
|
|
78
|
+
const engine = new RTCEngine({
|
|
79
|
+
...roomOptionDefaults,
|
|
80
|
+
packetTrailer: { worker: {} as Worker },
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(makeRTCConfiguration(engine).encodedInsertableStreams).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('does not enable encoded insertable streams for packet trailers when script transforms are supported', () => {
|
|
87
|
+
stubInsertableStreamsSupport();
|
|
88
|
+
stubScriptTransformSupport();
|
|
89
|
+
|
|
90
|
+
const engine = new RTCEngine({
|
|
91
|
+
...roomOptionDefaults,
|
|
92
|
+
packetTrailer: { worker: {} as Worker },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(makeRTCConfiguration(engine).encodedInsertableStreams).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('enables encoded insertable streams for E2EE', () => {
|
|
99
|
+
stubInsertableStreamsSupport();
|
|
100
|
+
|
|
101
|
+
const engine = new RTCEngine(roomOptionDefaults);
|
|
102
|
+
(
|
|
103
|
+
engine as unknown as {
|
|
104
|
+
signalOpts: {
|
|
105
|
+
autoSubscribe: boolean;
|
|
106
|
+
maxRetries: number;
|
|
107
|
+
e2eeEnabled: boolean;
|
|
108
|
+
websocketTimeout: number;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
).signalOpts = {
|
|
112
|
+
autoSubscribe: true,
|
|
113
|
+
maxRetries: 1,
|
|
114
|
+
e2eeEnabled: true,
|
|
115
|
+
websocketTimeout: 15_000,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
expect(makeRTCConfiguration(engine).encodedInsertableStreams).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('does not create sender encoded streams when packetTrailer has no worker', () => {
|
|
122
|
+
const engine = new RTCEngine({
|
|
123
|
+
...roomOptionDefaults,
|
|
124
|
+
packetTrailer: {} as never,
|
|
125
|
+
});
|
|
126
|
+
const createEncodedStreams = vi.fn();
|
|
127
|
+
const sender = {
|
|
128
|
+
createEncodedStreams,
|
|
129
|
+
} as unknown as RTCRtpSender;
|
|
130
|
+
|
|
131
|
+
setupPacketTrailerSender(engine, sender);
|
|
132
|
+
|
|
133
|
+
expect(createEncodedStreams).not.toHaveBeenCalled();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('does not create sender passthrough streams for packet trailers when script transforms are supported', () => {
|
|
137
|
+
stubScriptTransformSupport();
|
|
138
|
+
|
|
139
|
+
const engine = new RTCEngine({
|
|
140
|
+
...roomOptionDefaults,
|
|
141
|
+
packetTrailer: { worker: {} as Worker },
|
|
142
|
+
});
|
|
143
|
+
const createEncodedStreams = vi.fn();
|
|
144
|
+
const sender = {
|
|
145
|
+
createEncodedStreams,
|
|
146
|
+
} as unknown as RTCRtpSender;
|
|
147
|
+
|
|
148
|
+
setupPacketTrailerSender(engine, sender);
|
|
149
|
+
|
|
150
|
+
expect(createEncodedStreams).not.toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('posts sender encode streams to the packet trailer worker when write features are enabled', () => {
|
|
154
|
+
stubInsertableStreamsSupport();
|
|
155
|
+
|
|
156
|
+
const worker = { postMessage: vi.fn() } as unknown as Worker;
|
|
157
|
+
const engine = new RTCEngine({
|
|
158
|
+
...roomOptionDefaults,
|
|
159
|
+
packetTrailer: { worker },
|
|
160
|
+
});
|
|
161
|
+
const readable = {} as ReadableStream;
|
|
162
|
+
const writable = {} as WritableStream;
|
|
163
|
+
const createEncodedStreams = vi.fn(() => ({ readable, writable }));
|
|
164
|
+
const sender = {
|
|
165
|
+
createEncodedStreams,
|
|
166
|
+
} as unknown as RTCRtpSender;
|
|
167
|
+
|
|
168
|
+
setupPacketTrailerSender(engine, sender, { packetTrailer: { timestamp: true, frameId: true } });
|
|
169
|
+
|
|
170
|
+
expect(createEncodedStreams).toHaveBeenCalledTimes(1);
|
|
171
|
+
expect(worker.postMessage).toHaveBeenCalledWith(
|
|
172
|
+
{
|
|
173
|
+
kind: 'encode',
|
|
174
|
+
data: {
|
|
175
|
+
readableStream: readable,
|
|
176
|
+
writableStream: writable,
|
|
177
|
+
packetTrailer: { timestamp: true, frameId: true },
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
[readable, writable],
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('uses RTCRtpScriptTransform for sender packet trailer writes when supported', () => {
|
|
185
|
+
stubScriptTransformSupport();
|
|
186
|
+
|
|
187
|
+
const transform = {};
|
|
188
|
+
const RTCRtpScriptTransform = vi.fn(() => transform);
|
|
189
|
+
Object.defineProperty(window, 'RTCRtpScriptTransform', {
|
|
190
|
+
configurable: true,
|
|
191
|
+
value: RTCRtpScriptTransform,
|
|
192
|
+
writable: true,
|
|
193
|
+
});
|
|
194
|
+
Object.defineProperty(globalThis, 'RTCRtpScriptTransform', {
|
|
195
|
+
configurable: true,
|
|
196
|
+
value: RTCRtpScriptTransform,
|
|
197
|
+
writable: true,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const worker = {} as Worker;
|
|
201
|
+
const engine = new RTCEngine({
|
|
202
|
+
...roomOptionDefaults,
|
|
203
|
+
packetTrailer: { worker },
|
|
204
|
+
});
|
|
205
|
+
const createEncodedStreams = vi.fn();
|
|
206
|
+
const sender = {
|
|
207
|
+
createEncodedStreams,
|
|
208
|
+
} as unknown as RTCRtpSender;
|
|
209
|
+
|
|
210
|
+
setupPacketTrailerSender(engine, sender, { packetTrailer: { timestamp: true } });
|
|
211
|
+
|
|
212
|
+
expect(RTCRtpScriptTransform).toHaveBeenCalledWith(worker, {
|
|
213
|
+
kind: 'encode',
|
|
214
|
+
packetTrailer: { timestamp: true },
|
|
215
|
+
});
|
|
216
|
+
expect((sender as unknown as { transform: unknown }).transform).toBe(transform);
|
|
217
|
+
expect(createEncodedStreams).not.toHaveBeenCalled();
|
|
218
|
+
});
|
|
219
|
+
});
|
package/src/room/RTCEngine.ts
CHANGED
|
@@ -54,9 +54,14 @@ import {
|
|
|
54
54
|
toProtoSessionDescription,
|
|
55
55
|
} from '../api/SignalClient';
|
|
56
56
|
import type { BaseE2EEManager } from '../e2ee/E2eeManager';
|
|
57
|
-
import { asEncryptablePacket } from '../e2ee/utils';
|
|
57
|
+
import { asEncryptablePacket, isInsertableStreamSupported } from '../e2ee/utils';
|
|
58
58
|
import log, { LoggerNames, getLogger } from '../logger';
|
|
59
59
|
import type { InternalRoomOptions } from '../options';
|
|
60
|
+
import {
|
|
61
|
+
hasPacketTrailerPublishOptions,
|
|
62
|
+
isPacketTrailerSupported,
|
|
63
|
+
shouldUsePacketTrailerScriptTransform,
|
|
64
|
+
} from '../packetTrailer/utils';
|
|
60
65
|
import TypedPromise from '../utils/TypedPromise';
|
|
61
66
|
import { DataPacketBuffer } from '../utils/dataPacketBuffer';
|
|
62
67
|
import { TTLMap } from '../utils/ttlmap';
|
|
@@ -762,7 +767,14 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
762
767
|
): RTCConfiguration {
|
|
763
768
|
const rtcConfig = { ...this.rtcConfig };
|
|
764
769
|
|
|
765
|
-
|
|
770
|
+
// E2EE and packet trailer extraction both rely on encoded frame transforms.
|
|
771
|
+
// Only opt into the createEncodedStreams flavor when that path will be
|
|
772
|
+
// used; RTCRtpScriptTransform does not need the PeerConnection flag.
|
|
773
|
+
const needsInsertableStreams =
|
|
774
|
+
this.signalOpts?.e2eeEnabled ||
|
|
775
|
+
(this.options.packetTrailer?.worker && !shouldUsePacketTrailerScriptTransform());
|
|
776
|
+
|
|
777
|
+
if (needsInsertableStreams && isInsertableStreamSupported()) {
|
|
766
778
|
this.log.debug('E2EE - setting up transports with insertable streams');
|
|
767
779
|
// this makes sure that no data is sent before the transforms are ready
|
|
768
780
|
// @ts-ignore
|
|
@@ -940,8 +952,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
940
952
|
return;
|
|
941
953
|
}
|
|
942
954
|
const decryptedData = await this.e2eeManager?.handleEncryptedData(
|
|
943
|
-
dp.value.value.encryptedValue,
|
|
944
|
-
dp.value.value.iv,
|
|
955
|
+
dp.value.value.encryptedValue as NonSharedUint8Array,
|
|
956
|
+
dp.value.value.iv as NonSharedUint8Array,
|
|
945
957
|
dp.participantIdentity,
|
|
946
958
|
dp.value.value.keyIndex,
|
|
947
959
|
);
|
|
@@ -1004,16 +1016,17 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1004
1016
|
opts: TrackPublishOptions,
|
|
1005
1017
|
encodings?: RTCRtpEncodingParameters[],
|
|
1006
1018
|
) {
|
|
1019
|
+
let sender: RTCRtpSender;
|
|
1007
1020
|
if (supportsTransceiver()) {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
}
|
|
1011
|
-
if (supportsAddTrack()) {
|
|
1021
|
+
sender = await this.createTransceiverRTCRtpSender(track, opts, encodings);
|
|
1022
|
+
} else if (supportsAddTrack()) {
|
|
1012
1023
|
this.log.warn('using add-track fallback');
|
|
1013
|
-
|
|
1014
|
-
|
|
1024
|
+
sender = await this.createRTCRtpSender(track.mediaStreamTrack);
|
|
1025
|
+
} else {
|
|
1026
|
+
throw new UnexpectedConnectionState('Required webRTC APIs not supported on this device');
|
|
1015
1027
|
}
|
|
1016
|
-
|
|
1028
|
+
this.setupPacketTrailerSender(sender, opts);
|
|
1029
|
+
return sender;
|
|
1017
1030
|
}
|
|
1018
1031
|
|
|
1019
1032
|
async createSimulcastSender(
|
|
@@ -1022,16 +1035,67 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1022
1035
|
opts: TrackPublishOptions,
|
|
1023
1036
|
encodings?: RTCRtpEncodingParameters[],
|
|
1024
1037
|
) {
|
|
1025
|
-
|
|
1038
|
+
let sender: RTCRtpSender | undefined;
|
|
1026
1039
|
if (supportsTransceiver()) {
|
|
1027
|
-
|
|
1028
|
-
}
|
|
1029
|
-
if (supportsAddTrack()) {
|
|
1040
|
+
sender = await this.createSimulcastTransceiverSender(track, simulcastTrack, opts, encodings);
|
|
1041
|
+
} else if (supportsAddTrack()) {
|
|
1030
1042
|
this.log.debug('using add-track fallback');
|
|
1031
|
-
|
|
1043
|
+
sender = await this.createRTCRtpSender(track.mediaStreamTrack);
|
|
1044
|
+
} else {
|
|
1045
|
+
throw new UnexpectedConnectionState('Cannot stream on this device');
|
|
1032
1046
|
}
|
|
1047
|
+
if (sender) {
|
|
1048
|
+
this.setupPacketTrailerSender(sender, opts);
|
|
1049
|
+
}
|
|
1050
|
+
return sender;
|
|
1051
|
+
}
|
|
1033
1052
|
|
|
1034
|
-
|
|
1053
|
+
private setupPacketTrailerSender(sender: RTCRtpSender, opts: TrackPublishOptions = {}) {
|
|
1054
|
+
if (!this.options.packetTrailer?.worker || this.signalOpts?.e2eeEnabled) {
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const packetTrailer = opts.packetTrailer;
|
|
1059
|
+
const hasPacketTrailer = hasPacketTrailerPublishOptions(packetTrailer);
|
|
1060
|
+
|
|
1061
|
+
if (shouldUsePacketTrailerScriptTransform()) {
|
|
1062
|
+
if (hasPacketTrailer) {
|
|
1063
|
+
// @ts-ignore
|
|
1064
|
+
sender.transform = new RTCRtpScriptTransform(this.options.packetTrailer.worker, {
|
|
1065
|
+
kind: 'encode',
|
|
1066
|
+
packetTrailer,
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
if (
|
|
1073
|
+
!isPacketTrailerSupported(this.options.packetTrailer) ||
|
|
1074
|
+
!('createEncodedStreams' in sender)
|
|
1075
|
+
) {
|
|
1076
|
+
if (hasPacketTrailer) {
|
|
1077
|
+
this.log.warn('packet trailer transform not supported; skipping write', this.logContext);
|
|
1078
|
+
}
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// @ts-ignore
|
|
1083
|
+
const { readable, writable } = sender.createEncodedStreams();
|
|
1084
|
+
if (hasPacketTrailer) {
|
|
1085
|
+
this.options.packetTrailer.worker.postMessage(
|
|
1086
|
+
{
|
|
1087
|
+
kind: 'encode',
|
|
1088
|
+
data: {
|
|
1089
|
+
readableStream: readable,
|
|
1090
|
+
writableStream: writable,
|
|
1091
|
+
packetTrailer,
|
|
1092
|
+
},
|
|
1093
|
+
},
|
|
1094
|
+
[readable, writable],
|
|
1095
|
+
);
|
|
1096
|
+
} else {
|
|
1097
|
+
readable.pipeTo(writable);
|
|
1098
|
+
}
|
|
1035
1099
|
}
|
|
1036
1100
|
|
|
1037
1101
|
private async createTransceiverRTCRtpSender(
|
|
@@ -1447,7 +1511,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1447
1511
|
if (this.e2eeManager && this.e2eeManager.isDataChannelEncryptionEnabled) {
|
|
1448
1512
|
const encryptablePacket = asEncryptablePacket(packet);
|
|
1449
1513
|
if (encryptablePacket) {
|
|
1450
|
-
const encryptedData = await this.e2eeManager.encryptData(
|
|
1514
|
+
const encryptedData = await this.e2eeManager.encryptData(
|
|
1515
|
+
encryptablePacket.toBinary() as NonSharedUint8Array,
|
|
1516
|
+
);
|
|
1451
1517
|
packet.value = {
|
|
1452
1518
|
case: 'encryptedPacket',
|
|
1453
1519
|
value: new EncryptedPacket({
|
|
@@ -1464,7 +1530,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1464
1530
|
this.reliableDataSequence += 1;
|
|
1465
1531
|
}
|
|
1466
1532
|
|
|
1467
|
-
const msg = packet.toBinary()
|
|
1533
|
+
const msg = packet.toBinary() as Uint8Array<ArrayBuffer>;
|
|
1468
1534
|
|
|
1469
1535
|
switch (kind) {
|
|
1470
1536
|
case DataChannelKind.LOSSY:
|
|
@@ -1491,7 +1557,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1491
1557
|
|
|
1492
1558
|
/* @internal */
|
|
1493
1559
|
async sendLossyBytes(
|
|
1494
|
-
bytes:
|
|
1560
|
+
bytes: NonSharedUint8Array,
|
|
1495
1561
|
kind: Exclude<DataChannelKind, DataChannelKind.RELIABLE>,
|
|
1496
1562
|
bufferStatusLowBehavior: 'drop' | 'wait' = 'drop',
|
|
1497
1563
|
) {
|
package/src/room/Room.test.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ClientInfo_Capability, JoinResponse } from '@livekit/protocol';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
3
|
import Room from './Room';
|
|
4
|
+
import { roomConnectOptionDefaults, roomOptionDefaults } from './defaults';
|
|
3
5
|
import { RoomEvent } from './events';
|
|
4
6
|
|
|
5
7
|
describe('Active device switch', () => {
|
|
@@ -28,3 +30,62 @@ describe('Active device switch', () => {
|
|
|
28
30
|
expect(kind).toBe('audioinput');
|
|
29
31
|
});
|
|
30
32
|
});
|
|
33
|
+
|
|
34
|
+
describe('Room signaling options', () => {
|
|
35
|
+
it('advertises packet trailer capability when E2EE can handle trailers', async () => {
|
|
36
|
+
const room = new Room();
|
|
37
|
+
const join = vi.fn().mockResolvedValue({
|
|
38
|
+
joinResponse: new JoinResponse({
|
|
39
|
+
room: { name: 'test-room', sid: 'room-sid' },
|
|
40
|
+
participant: { sid: 'participant-sid', identity: 'test-user' },
|
|
41
|
+
}),
|
|
42
|
+
serverInfo: { version: '1.0.0' },
|
|
43
|
+
});
|
|
44
|
+
const engine = { join };
|
|
45
|
+
|
|
46
|
+
(
|
|
47
|
+
room as unknown as {
|
|
48
|
+
e2eeManager: unknown;
|
|
49
|
+
connectSignal: (
|
|
50
|
+
url: string,
|
|
51
|
+
token: string,
|
|
52
|
+
engine: unknown,
|
|
53
|
+
connectOptions: typeof roomConnectOptionDefaults,
|
|
54
|
+
roomOptions: typeof roomOptionDefaults,
|
|
55
|
+
abortController: AbortController,
|
|
56
|
+
) => Promise<JoinResponse>;
|
|
57
|
+
}
|
|
58
|
+
).e2eeManager = {};
|
|
59
|
+
|
|
60
|
+
await (
|
|
61
|
+
room as unknown as {
|
|
62
|
+
connectSignal: (
|
|
63
|
+
url: string,
|
|
64
|
+
token: string,
|
|
65
|
+
engine: unknown,
|
|
66
|
+
connectOptions: typeof roomConnectOptionDefaults,
|
|
67
|
+
roomOptions: typeof roomOptionDefaults,
|
|
68
|
+
abortController: AbortController,
|
|
69
|
+
) => Promise<JoinResponse>;
|
|
70
|
+
}
|
|
71
|
+
).connectSignal(
|
|
72
|
+
'wss://test.livekit.io',
|
|
73
|
+
'test-token',
|
|
74
|
+
engine,
|
|
75
|
+
roomConnectOptionDefaults,
|
|
76
|
+
roomOptionDefaults,
|
|
77
|
+
new AbortController(),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(join).toHaveBeenCalledWith(
|
|
81
|
+
'wss://test.livekit.io',
|
|
82
|
+
'test-token',
|
|
83
|
+
expect.objectContaining({
|
|
84
|
+
clientInfoCapabilities: [ClientInfo_Capability.CAP_PACKET_TRAILER],
|
|
85
|
+
e2eeEnabled: true,
|
|
86
|
+
}),
|
|
87
|
+
expect.any(AbortSignal),
|
|
88
|
+
false,
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
});
|