livekit-client 2.18.9 → 2.19.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 +5609 -644
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +3553 -2813
- 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 -4
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +7 -3
- 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 +8 -14
- 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/participant/RemoteParticipant.d.ts +5 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/rpc/client/RpcClientManager.d.ts +39 -0
- package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -0
- package/dist/src/room/rpc/client/events.d.ts +8 -0
- package/dist/src/room/rpc/client/events.d.ts.map +1 -0
- package/dist/src/room/rpc/index.d.ts +6 -0
- package/dist/src/room/rpc/index.d.ts.map +1 -0
- package/dist/src/room/rpc/server/RpcServerManager.d.ts +44 -0
- package/dist/src/room/rpc/server/RpcServerManager.d.ts.map +1 -0
- package/dist/src/room/rpc/server/events.d.ts +8 -0
- package/dist/src/room/rpc/server/events.d.ts.map +1 -0
- package/dist/src/room/{rpc.d.ts → rpc/utils.d.ts} +34 -4
- package/dist/src/room/rpc/utils.d.ts.map +1 -0
- 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 +9 -1
- package/dist/src/version.d.ts.map +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 -4
- package/dist/ts4.2/room/Room.d.ts +7 -3
- 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 +8 -14
- package/dist/ts4.2/room/participant/Participant.d.ts +1 -1
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +5 -1
- package/dist/ts4.2/room/rpc/client/RpcClientManager.d.ts +43 -0
- package/dist/ts4.2/room/rpc/client/events.d.ts +8 -0
- package/dist/ts4.2/room/rpc/index.d.ts +7 -0
- package/dist/ts4.2/room/rpc/server/RpcServerManager.d.ts +44 -0
- package/dist/ts4.2/room/rpc/server/events.d.ts +8 -0
- package/dist/ts4.2/room/{rpc.d.ts → rpc/utils.d.ts} +34 -4
- 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 +9 -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 -46
- package/src/room/Room.test.ts +62 -1
- package/src/room/Room.ts +111 -86
- 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 +64 -187
- package/src/room/participant/Participant.ts +1 -1
- package/src/room/participant/RemoteParticipant.ts +9 -0
- package/src/room/participant/publishUtils.ts +1 -1
- package/src/room/rpc/client/RpcClientManager.test.ts +430 -0
- package/src/room/rpc/client/RpcClientManager.ts +269 -0
- package/src/room/rpc/client/events.ts +9 -0
- package/src/room/rpc/index.ts +14 -0
- package/src/room/rpc/server/RpcServerManager.test.ts +471 -0
- package/src/room/rpc/server/RpcServerManager.ts +293 -0
- package/src/room/rpc/server/events.ts +9 -0
- package/src/room/{rpc.ts → rpc/utils.ts} +49 -8
- 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 +19 -4
- 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 +11 -1
- package/dist/src/room/rpc.d.ts.map +0 -1
- package/src/room/rpc.test.ts +0 -301
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { DataPacket, DataPacket_Kind, RpcAck, RpcRequest, RpcResponse } from '@livekit/protocol';
|
|
2
|
+
import EventEmitter from 'events';
|
|
3
|
+
import type TypedEmitter from 'typed-emitter';
|
|
4
|
+
import { type StructuredLogger } from '../../../logger';
|
|
5
|
+
import { CLIENT_PROTOCOL_DATA_STREAM_RPC } from '../../../version';
|
|
6
|
+
import { type TextStreamReader } from '../../data-stream/incoming/StreamReader';
|
|
7
|
+
import type OutgoingDataStreamManager from '../../data-stream/outgoing/OutgoingDataStreamManager';
|
|
8
|
+
import type Participant from '../../participant/Participant';
|
|
9
|
+
import {
|
|
10
|
+
MAX_V1_PAYLOAD_BYTES,
|
|
11
|
+
RPC_RESPONSE_DATA_STREAM_TOPIC,
|
|
12
|
+
RPC_VERSION_V2,
|
|
13
|
+
RpcError,
|
|
14
|
+
type RpcInvocationData,
|
|
15
|
+
RpcRequestAttrs,
|
|
16
|
+
byteLength,
|
|
17
|
+
} from '../utils';
|
|
18
|
+
import type { RpcServerManagerCallbacks } from './events';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Manages the server (handler) side of RPC: processing incoming requests,
|
|
22
|
+
* managing registered method handlers, and sending responses.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export default class RpcServerManager extends (EventEmitter as new () => TypedEmitter<RpcServerManagerCallbacks>) {
|
|
26
|
+
private log: StructuredLogger;
|
|
27
|
+
|
|
28
|
+
private outgoingDataStreamManager: OutgoingDataStreamManager;
|
|
29
|
+
|
|
30
|
+
private getRemoteParticipantClientProtocol: (identity: Participant['identity']) => number;
|
|
31
|
+
|
|
32
|
+
private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>> = new Map();
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
log: StructuredLogger,
|
|
36
|
+
outgoingDataStreamManager: OutgoingDataStreamManager,
|
|
37
|
+
getRemoteParticipantClientProtocol: (identity: Participant['identity']) => number,
|
|
38
|
+
) {
|
|
39
|
+
super();
|
|
40
|
+
this.log = log;
|
|
41
|
+
this.outgoingDataStreamManager = outgoingDataStreamManager;
|
|
42
|
+
this.getRemoteParticipantClientProtocol = getRemoteParticipantClientProtocol;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
registerRpcMethod(method: string, handler: (data: RpcInvocationData) => Promise<string>) {
|
|
46
|
+
if (this.rpcHandlers.has(method)) {
|
|
47
|
+
throw Error(
|
|
48
|
+
`RPC handler already registered for method ${method}, unregisterRpcMethod before trying to register again`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
this.rpcHandlers.set(method, handler);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
unregisterRpcMethod(method: string) {
|
|
55
|
+
this.rpcHandlers.delete(method);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Handle an incoming RPCRequest message containing a payload.
|
|
60
|
+
* This handles "version 1" of rpc requests.
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
async handleIncomingRpcRequest(callerIdentity: string, rpcRequest: RpcRequest) {
|
|
64
|
+
this.publishRpcAck(callerIdentity, rpcRequest.id);
|
|
65
|
+
|
|
66
|
+
if (rpcRequest.version !== 1) {
|
|
67
|
+
this.publishRpcResponsePacket(
|
|
68
|
+
callerIdentity,
|
|
69
|
+
rpcRequest.id,
|
|
70
|
+
null,
|
|
71
|
+
RpcError.builtIn('UNSUPPORTED_VERSION'),
|
|
72
|
+
);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const handler = this.rpcHandlers.get(rpcRequest.method);
|
|
77
|
+
|
|
78
|
+
if (!handler) {
|
|
79
|
+
this.publishRpcResponsePacket(
|
|
80
|
+
callerIdentity,
|
|
81
|
+
rpcRequest.id,
|
|
82
|
+
null,
|
|
83
|
+
RpcError.builtIn('UNSUPPORTED_METHOD'),
|
|
84
|
+
);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let response;
|
|
89
|
+
try {
|
|
90
|
+
response = await handler({
|
|
91
|
+
requestId: rpcRequest.id,
|
|
92
|
+
callerIdentity,
|
|
93
|
+
payload: rpcRequest.payload,
|
|
94
|
+
responseTimeout: rpcRequest.responseTimeoutMs,
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
let responseError;
|
|
98
|
+
if (error instanceof RpcError) {
|
|
99
|
+
responseError = error;
|
|
100
|
+
} else {
|
|
101
|
+
this.log.warn(
|
|
102
|
+
`Uncaught error returned by RPC handler for ${rpcRequest.method}. Returning APPLICATION_ERROR instead.`,
|
|
103
|
+
error,
|
|
104
|
+
);
|
|
105
|
+
responseError = RpcError.builtIn(
|
|
106
|
+
'APPLICATION_ERROR',
|
|
107
|
+
`Uncaught error: ${(error as Error)?.message ?? error}`,
|
|
108
|
+
{ cause: error },
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.publishRpcResponsePacket(callerIdentity, rpcRequest.id, null, responseError);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await this.publishRpcResponse(callerIdentity, rpcRequest.id, response ?? '');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Handle an incoming data stream containing a RPC request payload.
|
|
121
|
+
* This handles "version 2" of rpc requests.
|
|
122
|
+
* @internal
|
|
123
|
+
*/
|
|
124
|
+
async handleIncomingDataStream(
|
|
125
|
+
reader: TextStreamReader,
|
|
126
|
+
callerIdentity: Participant['identity'],
|
|
127
|
+
dataStreamAttrs: Record<string, string>,
|
|
128
|
+
) {
|
|
129
|
+
const requestId = dataStreamAttrs[RpcRequestAttrs.RPC_REQUEST_ID];
|
|
130
|
+
const method = dataStreamAttrs[RpcRequestAttrs.RPC_REQUEST_METHOD];
|
|
131
|
+
const responseTimeout = parseInt(
|
|
132
|
+
dataStreamAttrs[RpcRequestAttrs.RPC_REQUEST_RESPONSE_TIMEOUT_MS],
|
|
133
|
+
10,
|
|
134
|
+
);
|
|
135
|
+
const version = parseInt(dataStreamAttrs[RpcRequestAttrs.RPC_REQUEST_VERSION], 10);
|
|
136
|
+
|
|
137
|
+
if (!requestId || !method || Number.isNaN(responseTimeout) || Number.isNaN(version)) {
|
|
138
|
+
this.log.warn(
|
|
139
|
+
`RPC data stream malformed: ${RpcRequestAttrs.RPC_REQUEST_ID} / ${RpcRequestAttrs.RPC_REQUEST_METHOD} / ${RpcRequestAttrs.RPC_REQUEST_RESPONSE_TIMEOUT_MS} / ${RpcRequestAttrs.RPC_REQUEST_VERSION} not set.`,
|
|
140
|
+
);
|
|
141
|
+
this.publishRpcResponsePacket(
|
|
142
|
+
callerIdentity,
|
|
143
|
+
requestId,
|
|
144
|
+
null,
|
|
145
|
+
RpcError.builtIn('APPLICATION_ERROR', 'RPC data stream malformed'),
|
|
146
|
+
);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.publishRpcAck(callerIdentity, requestId);
|
|
151
|
+
|
|
152
|
+
if (version !== RPC_VERSION_V2) {
|
|
153
|
+
this.publishRpcResponsePacket(
|
|
154
|
+
callerIdentity,
|
|
155
|
+
requestId,
|
|
156
|
+
null,
|
|
157
|
+
RpcError.builtIn('UNSUPPORTED_VERSION'),
|
|
158
|
+
);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let payload: string;
|
|
163
|
+
try {
|
|
164
|
+
payload = await reader.readAll();
|
|
165
|
+
} catch (e) {
|
|
166
|
+
this.log.warn(`Error reading RPC request payload: ${e}`);
|
|
167
|
+
this.publishRpcResponsePacket(
|
|
168
|
+
callerIdentity,
|
|
169
|
+
requestId,
|
|
170
|
+
null,
|
|
171
|
+
RpcError.builtIn('APPLICATION_ERROR', 'Error reading RPC request payload', { cause: e }),
|
|
172
|
+
);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const handler = this.rpcHandlers.get(method);
|
|
177
|
+
|
|
178
|
+
if (!handler) {
|
|
179
|
+
this.publishRpcResponsePacket(
|
|
180
|
+
callerIdentity,
|
|
181
|
+
requestId,
|
|
182
|
+
null,
|
|
183
|
+
RpcError.builtIn('UNSUPPORTED_METHOD'),
|
|
184
|
+
);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let response;
|
|
189
|
+
try {
|
|
190
|
+
response = await handler({
|
|
191
|
+
requestId,
|
|
192
|
+
callerIdentity,
|
|
193
|
+
payload,
|
|
194
|
+
responseTimeout,
|
|
195
|
+
});
|
|
196
|
+
} catch (error) {
|
|
197
|
+
let responseError;
|
|
198
|
+
if (error instanceof RpcError) {
|
|
199
|
+
responseError = error;
|
|
200
|
+
} else {
|
|
201
|
+
this.log.warn(
|
|
202
|
+
`Uncaught error returned by RPC handler for ${method}. Returning APPLICATION_ERROR instead.`,
|
|
203
|
+
error,
|
|
204
|
+
);
|
|
205
|
+
responseError = RpcError.builtIn('APPLICATION_ERROR');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
this.publishRpcResponsePacket(callerIdentity, requestId, null, responseError);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await this.publishRpcResponse(callerIdentity, requestId, response ?? '');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private publishRpcAck(destinationIdentity: string, requestId: string) {
|
|
216
|
+
this.emit('sendDataPacket', {
|
|
217
|
+
packet: new DataPacket({
|
|
218
|
+
destinationIdentities: [destinationIdentity],
|
|
219
|
+
kind: DataPacket_Kind.RELIABLE,
|
|
220
|
+
value: {
|
|
221
|
+
case: 'rpcAck',
|
|
222
|
+
value: new RpcAck({
|
|
223
|
+
requestId,
|
|
224
|
+
}),
|
|
225
|
+
},
|
|
226
|
+
}),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private publishRpcResponsePacket(
|
|
231
|
+
destinationIdentity: string,
|
|
232
|
+
requestId: string,
|
|
233
|
+
payload: string | null,
|
|
234
|
+
error: RpcError | null,
|
|
235
|
+
) {
|
|
236
|
+
this.emit('sendDataPacket', {
|
|
237
|
+
packet: new DataPacket({
|
|
238
|
+
destinationIdentities: [destinationIdentity],
|
|
239
|
+
kind: DataPacket_Kind.RELIABLE,
|
|
240
|
+
value: {
|
|
241
|
+
case: 'rpcResponse',
|
|
242
|
+
value: new RpcResponse({
|
|
243
|
+
requestId,
|
|
244
|
+
value: error
|
|
245
|
+
? { case: 'error', value: error.toProto() }
|
|
246
|
+
: { case: 'payload', value: payload ?? '' },
|
|
247
|
+
}),
|
|
248
|
+
},
|
|
249
|
+
}),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Send a successful RPC response payload, choosing the transport based on
|
|
255
|
+
* the caller's client protocol version.
|
|
256
|
+
*/
|
|
257
|
+
private async publishRpcResponse(
|
|
258
|
+
destinationIdentity: string,
|
|
259
|
+
requestId: string,
|
|
260
|
+
payload: string,
|
|
261
|
+
) {
|
|
262
|
+
const callerClientProtocol = this.getRemoteParticipantClientProtocol(destinationIdentity);
|
|
263
|
+
|
|
264
|
+
if (callerClientProtocol >= CLIENT_PROTOCOL_DATA_STREAM_RPC) {
|
|
265
|
+
// Send response as a data stream
|
|
266
|
+
const writer = await this.outgoingDataStreamManager.streamText({
|
|
267
|
+
topic: RPC_RESPONSE_DATA_STREAM_TOPIC,
|
|
268
|
+
destinationIdentities: [destinationIdentity],
|
|
269
|
+
attributes: { [RpcRequestAttrs.RPC_REQUEST_ID]: requestId },
|
|
270
|
+
});
|
|
271
|
+
await writer.write(payload);
|
|
272
|
+
await writer.close();
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Legacy client: enforce size limit and send uncompressed payload inline
|
|
277
|
+
const responseBytes = byteLength(payload);
|
|
278
|
+
if (responseBytes > MAX_V1_PAYLOAD_BYTES) {
|
|
279
|
+
this.log.warn(
|
|
280
|
+
`RPC Response payload too large for request ${requestId}. To send larger responses, consider updating the sending client.`,
|
|
281
|
+
);
|
|
282
|
+
this.publishRpcResponsePacket(
|
|
283
|
+
destinationIdentity,
|
|
284
|
+
requestId,
|
|
285
|
+
null,
|
|
286
|
+
RpcError.builtIn('RESPONSE_PAYLOAD_TOO_LARGE'),
|
|
287
|
+
);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
this.publishRpcResponsePacket(destinationIdentity, requestId, payload, null);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
|
|
2
|
-
//
|
|
3
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
4
1
|
import { RpcError as RpcError_Proto } from '@livekit/protocol';
|
|
5
2
|
|
|
6
3
|
/** Parameters for initiating an RPC call */
|
|
@@ -63,6 +60,9 @@ export class RpcError extends Error {
|
|
|
63
60
|
|
|
64
61
|
data?: string;
|
|
65
62
|
|
|
63
|
+
// More info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
|
|
64
|
+
cause?: unknown;
|
|
65
|
+
|
|
66
66
|
/**
|
|
67
67
|
* Creates an error object with the given code and message, plus an optional data payload.
|
|
68
68
|
*
|
|
@@ -70,11 +70,15 @@ export class RpcError extends Error {
|
|
|
70
70
|
*
|
|
71
71
|
* Error codes 1001-1999 are reserved for built-in errors (see RpcError.ErrorCode for their meanings).
|
|
72
72
|
*/
|
|
73
|
-
constructor(code: number, message: string, data?: string) {
|
|
73
|
+
constructor(code: number, message: string, data?: string, options?: { cause?: unknown }) {
|
|
74
74
|
super(message);
|
|
75
75
|
this.code = code;
|
|
76
76
|
this.message = truncateBytes(message, RpcError.MAX_MESSAGE_BYTES);
|
|
77
77
|
this.data = data ? truncateBytes(data, RpcError.MAX_DATA_BYTES) : undefined;
|
|
78
|
+
|
|
79
|
+
if (typeof options?.cause !== 'undefined') {
|
|
80
|
+
this.cause = options?.cause;
|
|
81
|
+
}
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
/**
|
|
@@ -133,16 +137,53 @@ export class RpcError extends Error {
|
|
|
133
137
|
*
|
|
134
138
|
* @internal
|
|
135
139
|
*/
|
|
136
|
-
static builtIn(
|
|
137
|
-
|
|
140
|
+
static builtIn(
|
|
141
|
+
key: keyof typeof RpcError.ErrorCode,
|
|
142
|
+
data?: string,
|
|
143
|
+
options?: { cause?: unknown },
|
|
144
|
+
): RpcError {
|
|
145
|
+
return new RpcError(RpcError.ErrorCode[key], RpcError.ErrorMessage[key], data, options);
|
|
138
146
|
}
|
|
139
147
|
}
|
|
140
148
|
|
|
141
149
|
/*
|
|
142
|
-
* Maximum payload size for RPC requests and responses
|
|
150
|
+
* Maximum payload size for RPC requests and responses for clients with a clientProtocol of less
|
|
151
|
+
* than CLIENT_PROTOCOL_DATA_STREAM_RPC.
|
|
152
|
+
*
|
|
153
|
+
* If a payload exceeds this size and the remote client does not support compression,
|
|
143
154
|
* the RPC call will fail with a REQUEST_PAYLOAD_TOO_LARGE(1402) or RESPONSE_PAYLOAD_TOO_LARGE(1504) error.
|
|
144
155
|
*/
|
|
145
|
-
export const
|
|
156
|
+
export const MAX_V1_PAYLOAD_BYTES = 15360; // 15 KB
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Topic used for v2 RPC request data streams.
|
|
160
|
+
* @internal
|
|
161
|
+
*/
|
|
162
|
+
export const RPC_REQUEST_DATA_STREAM_TOPIC = 'lk.rpc_request';
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Topic used for v2 RPC response data streams.
|
|
166
|
+
* @internal
|
|
167
|
+
*/
|
|
168
|
+
export const RPC_RESPONSE_DATA_STREAM_TOPIC = 'lk.rpc_response';
|
|
169
|
+
|
|
170
|
+
/** @internal */
|
|
171
|
+
export enum RpcRequestAttrs {
|
|
172
|
+
RPC_REQUEST_ID = 'lk.rpc_request_id',
|
|
173
|
+
RPC_REQUEST_METHOD = 'lk.rpc_request_method',
|
|
174
|
+
RPC_REQUEST_RESPONSE_TIMEOUT_MS = 'lk.rpc_request_response_timeout_ms',
|
|
175
|
+
RPC_REQUEST_VERSION = 'lk.rpc_request_version',
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Initial version of rpc which uses RpcRequest / RpcResponse messages.
|
|
179
|
+
* @internal
|
|
180
|
+
**/
|
|
181
|
+
export const RPC_VERSION_V1 = 1;
|
|
182
|
+
|
|
183
|
+
/** Rpc version backed by data streams instead of RpcRequest / RpcResponse.
|
|
184
|
+
* @internal
|
|
185
|
+
**/
|
|
186
|
+
export const RPC_VERSION_V2 = 2;
|
|
146
187
|
|
|
147
188
|
/**
|
|
148
189
|
* @internal
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { PacketTrailerMetadata } from '../../packetTrailer/types';
|
|
2
|
+
|
|
3
|
+
const MAX_ENTRIES = 300;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Caches packet trailer metadata extracted from received video frames,
|
|
7
|
+
* keyed by RTP timestamp so it can be looked up when the frame is displayed.
|
|
8
|
+
*
|
|
9
|
+
* Metadata is populated either by the packet trailer worker managed by
|
|
10
|
+
* `PacketTrailerManager` (non-E2EE) or by the E2EE FrameCryptor worker
|
|
11
|
+
* after decryption (E2EE).
|
|
12
|
+
*
|
|
13
|
+
* @experimental
|
|
14
|
+
*/
|
|
15
|
+
export class PacketTrailerExtractor {
|
|
16
|
+
private metadataMap = new Map<number, PacketTrailerMetadata>();
|
|
17
|
+
|
|
18
|
+
private activeSsrc: number = 0;
|
|
19
|
+
|
|
20
|
+
storeMetadata(rtpTimestamp: number, ssrc: number, metadata: PacketTrailerMetadata) {
|
|
21
|
+
// Simulcast layer switch: SSRC changed, flush stale entries from old layer.
|
|
22
|
+
if (this.activeSsrc !== 0 && this.activeSsrc !== ssrc) {
|
|
23
|
+
this.metadataMap.clear();
|
|
24
|
+
}
|
|
25
|
+
this.activeSsrc = ssrc;
|
|
26
|
+
|
|
27
|
+
while (this.metadataMap.size >= MAX_ENTRIES) {
|
|
28
|
+
const evicted = this.metadataMap.keys().next().value!;
|
|
29
|
+
this.metadataMap.delete(evicted);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.metadataMap.set(rtpTimestamp, metadata);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
lookupMetadata(rtpTimestamp: number): PacketTrailerMetadata | undefined {
|
|
36
|
+
return this.metadataMap.get(rtpTimestamp);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
dispose() {
|
|
40
|
+
this.metadataMap.clear();
|
|
41
|
+
this.activeSsrc = 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PacketTrailerMetadata } from '../../packetTrailer/types';
|
|
1
2
|
import { debounce } from '../debounce';
|
|
2
3
|
import { TrackEvent } from '../events';
|
|
3
4
|
import type { VideoReceiverStats } from '../stats';
|
|
@@ -6,6 +7,7 @@ import CriticalTimers from '../timers';
|
|
|
6
7
|
import type { LoggerOptions } from '../types';
|
|
7
8
|
import type { ObservableMediaElement } from '../utils';
|
|
8
9
|
import { getDevicePixelRatio, getIntersectionObserver, getResizeObserver, isWeb } from '../utils';
|
|
10
|
+
import type { PacketTrailerExtractor } from './PacketTrailerExtractor';
|
|
9
11
|
import RemoteTrack from './RemoteTrack';
|
|
10
12
|
import { Track, attachToElement, detachTrack } from './Track';
|
|
11
13
|
import type { AdaptiveStreamSettings } from './types';
|
|
@@ -23,6 +25,9 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
|
|
|
23
25
|
|
|
24
26
|
private lastDimensions?: Track.Dimensions;
|
|
25
27
|
|
|
28
|
+
/** @internal */
|
|
29
|
+
packetTrailerExtractor?: PacketTrailerExtractor;
|
|
30
|
+
|
|
26
31
|
constructor(
|
|
27
32
|
mediaTrack: MediaStreamTrack,
|
|
28
33
|
sid: string,
|
|
@@ -38,6 +43,23 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
|
|
|
38
43
|
return this.adaptiveStreamSettings !== undefined;
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Look up frame-level metadata for a given RTP timestamp.
|
|
48
|
+
* Use with the `TrackEvent.TimeSyncUpdate` event to correlate displayed frames
|
|
49
|
+
* with their capture-time metadata.
|
|
50
|
+
*
|
|
51
|
+
* Requires the room to be configured with the `packetTrailer` worker option
|
|
52
|
+
* and the publishing track to have packet trailer features enabled.
|
|
53
|
+
*
|
|
54
|
+
*/
|
|
55
|
+
lookupFrameMetadata({
|
|
56
|
+
rtpTimestamp,
|
|
57
|
+
}: {
|
|
58
|
+
rtpTimestamp: number;
|
|
59
|
+
}): PacketTrailerMetadata | undefined {
|
|
60
|
+
return this.packetTrailerExtractor?.lookupMetadata(rtpTimestamp);
|
|
61
|
+
}
|
|
62
|
+
|
|
41
63
|
override setStreamState(value: Track.StreamState) {
|
|
42
64
|
super.setStreamState(value);
|
|
43
65
|
this.log.debug('setStreamState', value);
|
|
@@ -140,12 +162,11 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
|
|
|
140
162
|
detach(): HTMLMediaElement[];
|
|
141
163
|
detach(element: HTMLMediaElement): HTMLMediaElement;
|
|
142
164
|
detach(element?: HTMLMediaElement): HTMLMediaElement | HTMLMediaElement[] {
|
|
143
|
-
let detachedElements: HTMLMediaElement[] = [];
|
|
144
165
|
if (element) {
|
|
145
166
|
this.stopObservingElement(element);
|
|
146
167
|
return super.detach(element);
|
|
147
168
|
}
|
|
148
|
-
detachedElements = super.detach();
|
|
169
|
+
const detachedElements = super.detach();
|
|
149
170
|
|
|
150
171
|
for (const e of detachedElements) {
|
|
151
172
|
this.stopObservingElement(e);
|
package/src/room/track/Track.ts
CHANGED
|
@@ -537,6 +537,6 @@ export type TrackEventCallbacks = {
|
|
|
537
537
|
trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
|
|
538
538
|
audioTrackFeatureUpdate: (track: any, feature: AudioTrackFeature, enabled: boolean) => void;
|
|
539
539
|
timeSyncUpdate: (update: { timestamp: number; rtpTimestamp: number }) => void;
|
|
540
|
-
preConnectBufferFlushed: (buffer:
|
|
540
|
+
preConnectBufferFlushed: (buffer: NonSharedUint8Array[]) => void;
|
|
541
541
|
cpuConstrained: () => void;
|
|
542
542
|
};
|
package/src/room/track/create.ts
CHANGED
|
@@ -111,10 +111,6 @@ export async function createLocalTracks(
|
|
|
111
111
|
return await Promise.all(
|
|
112
112
|
stream.getTracks().map(async (mediaStreamTrack) => {
|
|
113
113
|
const isAudio = mediaStreamTrack.kind === 'audio';
|
|
114
|
-
let trackOptions = isAudio ? opts!.audio : opts!.video;
|
|
115
|
-
if (typeof trackOptions === 'boolean' || !trackOptions) {
|
|
116
|
-
trackOptions = {};
|
|
117
|
-
}
|
|
118
114
|
let trackConstraints: MediaTrackConstraints | undefined;
|
|
119
115
|
const conOrBool = isAudio ? constraints.audio : constraints.video;
|
|
120
116
|
if (typeof conOrBool !== 'boolean') {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PacketTrailerPublishOptions } from '../../packetTrailer/types';
|
|
1
2
|
import type { Track } from './Track';
|
|
2
3
|
import type {
|
|
3
4
|
AudioProcessorOptions,
|
|
@@ -128,6 +129,16 @@ export interface TrackPublishDefaults {
|
|
|
128
129
|
* Defaults to false.
|
|
129
130
|
*/
|
|
130
131
|
preConnectBuffer?: boolean;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Packet trailer metadata to append to published video frames.
|
|
135
|
+
*
|
|
136
|
+
* Requires either room-level packet trailer worker configuration or E2EE,
|
|
137
|
+
* because encoded frame transforms are used to write the trailer.
|
|
138
|
+
*
|
|
139
|
+
* @experimental
|
|
140
|
+
*/
|
|
141
|
+
packetTrailer?: PacketTrailerPublishOptions;
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
/**
|
package/src/room/track/record.ts
CHANGED
|
@@ -51,7 +51,7 @@ export class LocalTrackRecorder<T extends LocalTrack> extends RecorderBase {
|
|
|
51
51
|
start: (controller) => {
|
|
52
52
|
streamController = controller;
|
|
53
53
|
dataListener = async (event: BlobEvent) => {
|
|
54
|
-
let data:
|
|
54
|
+
let data: NonSharedUint8Array;
|
|
55
55
|
|
|
56
56
|
if (event.data.arrayBuffer) {
|
|
57
57
|
const arrayBuffer = await event.data.arrayBuffer();
|
package/src/room/track/utils.ts
CHANGED
|
@@ -280,7 +280,10 @@ export function getLogContextFromTrack(track: Track | TrackPublication): Record<
|
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
export function supportsSynchronizationSources(): boolean {
|
|
283
|
-
return
|
|
283
|
+
return (
|
|
284
|
+
typeof RTCRtpReceiver !== 'undefined' &&
|
|
285
|
+
typeof RTCRtpReceiver.prototype.getSynchronizationSources === 'function'
|
|
286
|
+
);
|
|
284
287
|
}
|
|
285
288
|
|
|
286
289
|
export function diffAttributes(
|
package/src/room/utils.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { ClientInfo_Capability } from '@livekit/protocol';
|
|
1
2
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { extractMaxAgeFromRequestHeaders, splitUtf8, toWebsocketUrl } from './utils';
|
|
3
|
+
import { extractMaxAgeFromRequestHeaders, getClientInfo, splitUtf8, toWebsocketUrl } from './utils';
|
|
3
4
|
|
|
4
5
|
describe('toWebsocketUrl', () => {
|
|
5
6
|
it('leaves wss urls alone', () => {
|
|
@@ -15,6 +16,18 @@ describe('toWebsocketUrl', () => {
|
|
|
15
16
|
});
|
|
16
17
|
});
|
|
17
18
|
|
|
19
|
+
describe('getClientInfo', () => {
|
|
20
|
+
it('does not advertise packet trailer capability by default', () => {
|
|
21
|
+
expect(getClientInfo().capabilities).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('advertises packet trailer capability when provided', () => {
|
|
25
|
+
expect(getClientInfo([ClientInfo_Capability.CAP_PACKET_TRAILER]).capabilities).toEqual([
|
|
26
|
+
ClientInfo_Capability.CAP_PACKET_TRAILER,
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
18
31
|
describe('splitUtf8', () => {
|
|
19
32
|
it('splits a string into chunks of the given size', () => {
|
|
20
33
|
expect(splitUtf8('hello world', 5)).toEqual([
|
package/src/room/utils.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ChatMessage as ChatMessageModel,
|
|
3
3
|
ClientInfo,
|
|
4
|
+
ClientInfo_Capability,
|
|
4
5
|
ClientInfo_SDK,
|
|
5
6
|
DisconnectReason,
|
|
6
7
|
Transcription as TranscriptionModel,
|
|
@@ -9,7 +10,7 @@ import { type Throws } from '@livekit/throws-transformer/throws';
|
|
|
9
10
|
import TypedPromise from '../utils/TypedPromise';
|
|
10
11
|
import { getBrowser } from '../utils/browserParser';
|
|
11
12
|
import type { BrowserDetails } from '../utils/browserParser';
|
|
12
|
-
import { protocolVersion, version } from '../version';
|
|
13
|
+
import { clientProtocol, protocolVersion, version } from '../version';
|
|
13
14
|
import { type ConnectionError, ConnectionErrorReason } from './errors';
|
|
14
15
|
import type LocalParticipant from './participant/LocalParticipant';
|
|
15
16
|
import type Participant from './participant/Participant';
|
|
@@ -179,6 +180,18 @@ export function isChromiumBased(): boolean {
|
|
|
179
180
|
return !!browser && browser.name === 'Chrome' && browser.os !== 'iOS';
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
export function isScriptTransformSupportedForWorker(): boolean {
|
|
184
|
+
// Chrome occasionally throws an `InvalidState` error when using script transforms directly after introducing this API in 141.
|
|
185
|
+
// Disabling it for Chrome based browsers until the API has stabilized.
|
|
186
|
+
// @ts-ignore
|
|
187
|
+
return (
|
|
188
|
+
typeof window !== 'undefined' &&
|
|
189
|
+
// @ts-ignore
|
|
190
|
+
typeof window.RTCRtpScriptTransform !== 'undefined' &&
|
|
191
|
+
!isChromiumBased()
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
182
195
|
export function isSafari(): boolean {
|
|
183
196
|
return getBrowser()?.name === 'Safari';
|
|
184
197
|
}
|
|
@@ -364,10 +377,12 @@ export interface ObservableMediaElement extends HTMLMediaElement {
|
|
|
364
377
|
handleVisibilityChanged: (entry: IntersectionObserverEntry) => void;
|
|
365
378
|
}
|
|
366
379
|
|
|
367
|
-
export function getClientInfo(): ClientInfo {
|
|
380
|
+
export function getClientInfo(capabilities?: ClientInfo_Capability[]): ClientInfo {
|
|
368
381
|
const info = new ClientInfo({
|
|
382
|
+
capabilities,
|
|
369
383
|
sdk: ClientInfo_SDK.JS,
|
|
370
384
|
protocol: protocolVersion,
|
|
385
|
+
clientProtocol,
|
|
371
386
|
version,
|
|
372
387
|
});
|
|
373
388
|
|
|
@@ -740,12 +755,12 @@ export function isRemoteParticipant(p: Participant): p is RemoteParticipant {
|
|
|
740
755
|
return !p.isLocal;
|
|
741
756
|
}
|
|
742
757
|
|
|
743
|
-
export function splitUtf8(s: string, n: number):
|
|
758
|
+
export function splitUtf8(s: string, n: number): NonSharedUint8Array[] {
|
|
744
759
|
if (n < 4) {
|
|
745
760
|
throw new Error('n must be at least 4 due to utf8 encoding rules');
|
|
746
761
|
}
|
|
747
762
|
// adapted from https://stackoverflow.com/a/6043797
|
|
748
|
-
const result:
|
|
763
|
+
const result: NonSharedUint8Array[] = [];
|
|
749
764
|
let encoded = new TextEncoder().encode(s);
|
|
750
765
|
while (encoded.length > n) {
|
|
751
766
|
let k = n;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// As of TS 5.7, `Uint8Array` is generic over its backing buffer (`Uint8Array<ArrayBufferLike>`),
|
|
2
|
+
// which includes `SharedArrayBuffer`. Many Web APIs (WebCrypto, structured clone, RTCDataChannel)
|
|
3
|
+
// only accept the non-shared variant `Uint8Array<ArrayBuffer>`. Using `ReturnType<typeof Uint8Array.from>`
|
|
4
|
+
// resolves to that non-shared variant on TS versions that support the generic, while remaining
|
|
5
|
+
// equivalent to plain `Uint8Array` on older versions — so this alias works across the range we support.
|
|
6
|
+
type NonSharedUint8Array = ReturnType<typeof Uint8Array.from>;
|