livekit-client 2.8.0 → 2.9.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/README.md +18 -7
- 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 +1 -0
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +3685 -2966
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/index.d.ts +7 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +6 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +50 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/StreamReader.d.ts +56 -0
- package/dist/src/room/StreamReader.d.ts.map +1 -0
- package/dist/src/room/StreamWriter.d.ts +16 -0
- package/dist/src/room/StreamWriter.d.ts.map +1 -0
- package/dist/src/room/errors.d.ts +3 -1
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +23 -36
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +1 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrackPublication.d.ts +1 -0
- package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrack.d.ts +1 -0
- package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts +1 -0
- package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +1 -0
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/TrackPublication.d.ts +2 -1
- package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/create.d.ts.map +1 -1
- package/dist/src/room/track/facingMode.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +18 -2
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +43 -0
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +26 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/ts4.2/src/index.d.ts +7 -5
- package/dist/ts4.2/src/room/RTCEngine.d.ts +6 -0
- package/dist/ts4.2/src/room/Room.d.ts +49 -0
- package/dist/ts4.2/src/room/StreamReader.d.ts +56 -0
- package/dist/ts4.2/src/room/StreamWriter.d.ts +25 -0
- package/dist/ts4.2/src/room/errors.d.ts +3 -1
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +23 -36
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -0
- package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -0
- package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +1 -0
- package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +1 -0
- package/dist/ts4.2/src/room/track/Track.d.ts +1 -0
- package/dist/ts4.2/src/room/track/TrackPublication.d.ts +2 -1
- package/dist/ts4.2/src/room/track/options.d.ts +18 -2
- package/dist/ts4.2/src/room/types.d.ts +43 -0
- package/dist/ts4.2/src/room/utils.d.ts +26 -0
- package/package.json +3 -3
- package/src/api/SignalClient.ts +5 -2
- package/src/e2ee/E2eeManager.ts +2 -2
- package/src/index.ts +17 -1
- package/src/room/RTCEngine.ts +69 -2
- package/src/room/Room.ts +317 -25
- package/src/room/StreamReader.ts +177 -0
- package/src/room/StreamWriter.ts +32 -0
- package/src/room/errors.ts +16 -2
- package/src/room/participant/LocalParticipant.ts +320 -165
- package/src/room/participant/Participant.ts +2 -5
- package/src/room/participant/RemoteParticipant.ts +3 -2
- package/src/room/{participant/LocalParticipant.test.ts → rpc.test.ts} +22 -29
- package/src/room/track/LocalAudioTrack.ts +4 -3
- package/src/room/track/LocalTrack.ts +12 -4
- package/src/room/track/LocalTrackPublication.ts +6 -1
- package/src/room/track/LocalVideoTrack.ts +1 -1
- package/src/room/track/RemoteAudioTrack.ts +1 -0
- package/src/room/track/RemoteTrack.ts +4 -0
- package/src/room/track/RemoteTrackPublication.ts +8 -4
- package/src/room/track/RemoteVideoTrack.ts +1 -0
- package/src/room/track/Track.ts +2 -0
- package/src/room/track/TrackPublication.ts +6 -3
- package/src/room/track/create.ts +4 -3
- package/src/room/track/facingMode.ts +2 -1
- package/src/room/track/options.ts +20 -2
- package/src/room/track/utils.ts +1 -1
- package/src/room/types.ts +50 -0
- package/src/room/utils.ts +77 -0
|
@@ -4,6 +4,12 @@ import {
|
|
|
4
4
|
Codec,
|
|
5
5
|
DataPacket,
|
|
6
6
|
DataPacket_Kind,
|
|
7
|
+
DataStream_ByteHeader,
|
|
8
|
+
DataStream_Chunk,
|
|
9
|
+
DataStream_Header,
|
|
10
|
+
DataStream_OperationType,
|
|
11
|
+
DataStream_TextHeader,
|
|
12
|
+
DataStream_Trailer,
|
|
7
13
|
Encryption_Type,
|
|
8
14
|
ParticipantInfo,
|
|
9
15
|
ParticipantPermission,
|
|
@@ -23,6 +29,7 @@ import {
|
|
|
23
29
|
import type { InternalRoomOptions } from '../../options';
|
|
24
30
|
import { PCTransportState } from '../PCTransportManager';
|
|
25
31
|
import type RTCEngine from '../RTCEngine';
|
|
32
|
+
import { TextStreamWriter } from '../StreamWriter';
|
|
26
33
|
import { defaultVideoCodec } from '../defaults';
|
|
27
34
|
import {
|
|
28
35
|
DeviceUnsupportedError,
|
|
@@ -61,15 +68,27 @@ import {
|
|
|
61
68
|
mimeTypeToVideoCodecString,
|
|
62
69
|
screenCaptureToDisplayMediaStreamOptions,
|
|
63
70
|
} from '../track/utils';
|
|
64
|
-
import
|
|
71
|
+
import {
|
|
72
|
+
type ChatMessage,
|
|
73
|
+
type DataPublishOptions,
|
|
74
|
+
type SendTextOptions,
|
|
75
|
+
type StreamTextOptions,
|
|
76
|
+
type TextStreamInfo,
|
|
77
|
+
} from '../types';
|
|
65
78
|
import {
|
|
66
79
|
Future,
|
|
67
80
|
compareVersions,
|
|
81
|
+
isAudioTrack,
|
|
68
82
|
isE2EESimulcastSupported,
|
|
69
83
|
isFireFox,
|
|
84
|
+
isLocalAudioTrack,
|
|
85
|
+
isLocalTrack,
|
|
86
|
+
isLocalVideoTrack,
|
|
70
87
|
isSVCCodec,
|
|
71
88
|
isSafari17,
|
|
89
|
+
isVideoTrack,
|
|
72
90
|
isWeb,
|
|
91
|
+
numberToBigInt,
|
|
73
92
|
sleep,
|
|
74
93
|
supportsAV1,
|
|
75
94
|
supportsVP9,
|
|
@@ -84,6 +103,8 @@ import {
|
|
|
84
103
|
mediaTrackToLocalTrack,
|
|
85
104
|
} from './publishUtils';
|
|
86
105
|
|
|
106
|
+
const STREAM_CHUNK_SIZE = 15_000;
|
|
107
|
+
|
|
87
108
|
export default class LocalParticipant extends Participant {
|
|
88
109
|
audioTrackPublications: Map<string, LocalTrackPublication>;
|
|
89
110
|
|
|
@@ -119,6 +140,8 @@ export default class LocalParticipant extends Participant {
|
|
|
119
140
|
|
|
120
141
|
private reconnectFuture?: Future<void>;
|
|
121
142
|
|
|
143
|
+
private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>>;
|
|
144
|
+
|
|
122
145
|
private pendingSignalRequests: Map<
|
|
123
146
|
number,
|
|
124
147
|
{
|
|
@@ -130,8 +153,6 @@ export default class LocalParticipant extends Participant {
|
|
|
130
153
|
|
|
131
154
|
private enabledPublishVideoCodecs: Codec[] = [];
|
|
132
155
|
|
|
133
|
-
private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>> = new Map();
|
|
134
|
-
|
|
135
156
|
private pendingAcks = new Map<string, { resolve: () => void; participantIdentity: string }>();
|
|
136
157
|
|
|
137
158
|
private pendingResponses = new Map<
|
|
@@ -143,7 +164,13 @@ export default class LocalParticipant extends Participant {
|
|
|
143
164
|
>();
|
|
144
165
|
|
|
145
166
|
/** @internal */
|
|
146
|
-
constructor(
|
|
167
|
+
constructor(
|
|
168
|
+
sid: string,
|
|
169
|
+
identity: string,
|
|
170
|
+
engine: RTCEngine,
|
|
171
|
+
options: InternalRoomOptions,
|
|
172
|
+
roomRpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>>,
|
|
173
|
+
) {
|
|
147
174
|
super(sid, identity, undefined, undefined, undefined, {
|
|
148
175
|
loggerName: options.loggerName,
|
|
149
176
|
loggerContextCb: () => this.engine.logContext,
|
|
@@ -160,6 +187,7 @@ export default class LocalParticipant extends Participant {
|
|
|
160
187
|
['audiooutput', 'default'],
|
|
161
188
|
]);
|
|
162
189
|
this.pendingSignalRequests = new Map();
|
|
190
|
+
this.rpcHandlers = roomRpcHandlers;
|
|
163
191
|
}
|
|
164
192
|
|
|
165
193
|
get lastCameraError(): Error | undefined {
|
|
@@ -251,17 +279,6 @@ export default class LocalParticipant extends Participant {
|
|
|
251
279
|
|
|
252
280
|
private handleDataPacket = (packet: DataPacket) => {
|
|
253
281
|
switch (packet.value.case) {
|
|
254
|
-
case 'rpcRequest':
|
|
255
|
-
let rpcRequest = packet.value.value as RpcRequest;
|
|
256
|
-
this.handleIncomingRpcRequest(
|
|
257
|
-
packet.participantIdentity,
|
|
258
|
-
rpcRequest.id,
|
|
259
|
-
rpcRequest.method,
|
|
260
|
-
rpcRequest.payload,
|
|
261
|
-
rpcRequest.responseTimeoutMs,
|
|
262
|
-
rpcRequest.version,
|
|
263
|
-
);
|
|
264
|
-
break;
|
|
265
282
|
case 'rpcResponse':
|
|
266
283
|
let rpcResponse = packet.value.value as RpcResponse;
|
|
267
284
|
let payload: string | null = null;
|
|
@@ -497,6 +514,7 @@ export default class LocalParticipant extends Participant {
|
|
|
497
514
|
if (e instanceof Error) {
|
|
498
515
|
this.emit(ParticipantEvent.MediaDevicesError, e);
|
|
499
516
|
}
|
|
517
|
+
this.pendingPublishing.delete(source);
|
|
500
518
|
throw e;
|
|
501
519
|
}
|
|
502
520
|
try {
|
|
@@ -640,9 +658,9 @@ export default class LocalParticipant extends Participant {
|
|
|
640
658
|
track.setAudioContext(this.audioContext);
|
|
641
659
|
}
|
|
642
660
|
track.mediaStream = stream;
|
|
643
|
-
if (track
|
|
661
|
+
if (isAudioTrack(track) && audioProcessor) {
|
|
644
662
|
await track.setProcessor(audioProcessor);
|
|
645
|
-
} else if (track
|
|
663
|
+
} else if (isVideoTrack(track) && videoProcessor) {
|
|
646
664
|
await track.setProcessor(videoProcessor);
|
|
647
665
|
}
|
|
648
666
|
return track;
|
|
@@ -717,7 +735,7 @@ export default class LocalParticipant extends Participant {
|
|
|
717
735
|
options?: TrackPublishOptions,
|
|
718
736
|
isRepublish = false,
|
|
719
737
|
): Promise<LocalTrackPublication> {
|
|
720
|
-
if (track
|
|
738
|
+
if (isLocalAudioTrack(track)) {
|
|
721
739
|
track.setAudioContext(this.audioContext);
|
|
722
740
|
}
|
|
723
741
|
|
|
@@ -725,7 +743,7 @@ export default class LocalParticipant extends Participant {
|
|
|
725
743
|
if (this.republishPromise && !isRepublish) {
|
|
726
744
|
await this.republishPromise;
|
|
727
745
|
}
|
|
728
|
-
if (track
|
|
746
|
+
if (isLocalTrack(track) && this.pendingPublishPromises.has(track)) {
|
|
729
747
|
await this.pendingPublishPromises.get(track);
|
|
730
748
|
}
|
|
731
749
|
let defaultConstraints: MediaTrackConstraints | undefined;
|
|
@@ -857,7 +875,7 @@ export default class LocalParticipant extends Participant {
|
|
|
857
875
|
|
|
858
876
|
private async publish(track: LocalTrack, opts: TrackPublishOptions, isStereo: boolean) {
|
|
859
877
|
const existingTrackOfSource = Array.from(this.trackPublications.values()).find(
|
|
860
|
-
(publishedTrack) => track
|
|
878
|
+
(publishedTrack) => isLocalTrack(track) && publishedTrack.source === track.source,
|
|
861
879
|
);
|
|
862
880
|
if (existingTrackOfSource && track.source !== Track.Source.Unknown) {
|
|
863
881
|
this.log.info(`publishing a second track with the same source: ${track.source}`, {
|
|
@@ -865,7 +883,7 @@ export default class LocalParticipant extends Participant {
|
|
|
865
883
|
...getLogContextFromTrack(track),
|
|
866
884
|
});
|
|
867
885
|
}
|
|
868
|
-
if (opts.stopMicTrackOnMute && track
|
|
886
|
+
if (opts.stopMicTrackOnMute && isAudioTrack(track)) {
|
|
869
887
|
track.stopOnMute = true;
|
|
870
888
|
}
|
|
871
889
|
|
|
@@ -919,6 +937,7 @@ export default class LocalParticipant extends Participant {
|
|
|
919
937
|
stereo: isStereo,
|
|
920
938
|
disableRed: this.isE2EEEnabled || !(opts.red ?? true),
|
|
921
939
|
stream: opts?.stream,
|
|
940
|
+
backupCodecPolicy: opts?.backupCodecPolicy,
|
|
922
941
|
});
|
|
923
942
|
|
|
924
943
|
// compute encodings and layers for video
|
|
@@ -950,7 +969,7 @@ export default class LocalParticipant extends Participant {
|
|
|
950
969
|
req.width = dims.width;
|
|
951
970
|
req.height = dims.height;
|
|
952
971
|
// for svc codecs, disable simulcast and use vp8 for backup codec
|
|
953
|
-
if (track
|
|
972
|
+
if (isLocalVideoTrack(track)) {
|
|
954
973
|
if (isSVCCodec(videoCodec)) {
|
|
955
974
|
if (track.source === Track.Source.ScreenShare) {
|
|
956
975
|
// vp9 svc with screenshare cannot encode multiple spatial layers
|
|
@@ -1036,7 +1055,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1036
1055
|
|
|
1037
1056
|
track.sender = await this.engine.createSender(track, opts, encodings);
|
|
1038
1057
|
|
|
1039
|
-
if (track
|
|
1058
|
+
if (isLocalVideoTrack(track)) {
|
|
1040
1059
|
opts.degradationPreference ??= getDefaultDegradationPreference(track);
|
|
1041
1060
|
track.setDegradationPreference(opts.degradationPreference);
|
|
1042
1061
|
}
|
|
@@ -1126,9 +1145,9 @@ export default class LocalParticipant extends Participant {
|
|
|
1126
1145
|
trackInfo: ti,
|
|
1127
1146
|
});
|
|
1128
1147
|
|
|
1129
|
-
if (track
|
|
1148
|
+
if (isLocalVideoTrack(track)) {
|
|
1130
1149
|
track.startMonitor(this.engine.client);
|
|
1131
|
-
} else if (track
|
|
1150
|
+
} else if (isLocalAudioTrack(track)) {
|
|
1132
1151
|
track.startMonitor();
|
|
1133
1152
|
}
|
|
1134
1153
|
|
|
@@ -1169,7 +1188,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1169
1188
|
throw new TrackInvalidError('track is not published');
|
|
1170
1189
|
}
|
|
1171
1190
|
|
|
1172
|
-
if (!(track
|
|
1191
|
+
if (!isLocalVideoTrack(track)) {
|
|
1173
1192
|
throw new TrackInvalidError('track is not a video track');
|
|
1174
1193
|
}
|
|
1175
1194
|
|
|
@@ -1236,7 +1255,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1236
1255
|
track: LocalTrack | MediaStreamTrack,
|
|
1237
1256
|
stopOnUnpublish?: boolean,
|
|
1238
1257
|
): Promise<LocalTrackPublication | undefined> {
|
|
1239
|
-
if (track
|
|
1258
|
+
if (isLocalTrack(track)) {
|
|
1240
1259
|
const publishPromise = this.pendingPublishPromises.get(track);
|
|
1241
1260
|
if (publishPromise) {
|
|
1242
1261
|
this.log.info('awaiting publish promise before attempting to unpublish', {
|
|
@@ -1303,7 +1322,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1303
1322
|
if (this.engine.removeTrack(trackSender)) {
|
|
1304
1323
|
negotiationNeeded = true;
|
|
1305
1324
|
}
|
|
1306
|
-
if (track
|
|
1325
|
+
if (isLocalVideoTrack(track)) {
|
|
1307
1326
|
for (const [, trackInfo] of track.simulcastCodecs) {
|
|
1308
1327
|
if (trackInfo.sender) {
|
|
1309
1328
|
if (this.engine.removeTrack(trackInfo.sender)) {
|
|
@@ -1349,9 +1368,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1349
1368
|
tracks: LocalTrack[] | MediaStreamTrack[],
|
|
1350
1369
|
): Promise<LocalTrackPublication[]> {
|
|
1351
1370
|
const results = await Promise.all(tracks.map((track) => this.unpublishTrack(track)));
|
|
1352
|
-
return results.filter(
|
|
1353
|
-
(track) => track instanceof LocalTrackPublication,
|
|
1354
|
-
) as LocalTrackPublication[];
|
|
1371
|
+
return results.filter((track) => !!track);
|
|
1355
1372
|
}
|
|
1356
1373
|
|
|
1357
1374
|
async republishAllTracks(options?: TrackPublishOptions, restartTracks: boolean = true) {
|
|
@@ -1379,7 +1396,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1379
1396
|
!track.isMuted &&
|
|
1380
1397
|
track.source !== Track.Source.ScreenShare &&
|
|
1381
1398
|
track.source !== Track.Source.ScreenShareAudio &&
|
|
1382
|
-
(track
|
|
1399
|
+
(isLocalAudioTrack(track) || isLocalVideoTrack(track)) &&
|
|
1383
1400
|
!track.isUserProvided
|
|
1384
1401
|
) {
|
|
1385
1402
|
// generally we need to restart the track before publishing, often a full reconnect
|
|
@@ -1453,11 +1470,12 @@ export default class LocalParticipant extends Participant {
|
|
|
1453
1470
|
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
|
1454
1471
|
}
|
|
1455
1472
|
|
|
1456
|
-
async sendChatMessage(text: string): Promise<ChatMessage> {
|
|
1473
|
+
async sendChatMessage(text: string, options?: SendTextOptions): Promise<ChatMessage> {
|
|
1457
1474
|
const msg = {
|
|
1458
1475
|
id: crypto.randomUUID(),
|
|
1459
1476
|
message: text,
|
|
1460
1477
|
timestamp: Date.now(),
|
|
1478
|
+
attachedFiles: options?.attachments,
|
|
1461
1479
|
} as const satisfies ChatMessage;
|
|
1462
1480
|
const packet = new DataPacket({
|
|
1463
1481
|
value: {
|
|
@@ -1469,6 +1487,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1469
1487
|
},
|
|
1470
1488
|
});
|
|
1471
1489
|
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
|
1490
|
+
|
|
1472
1491
|
this.emit(ParticipantEvent.ChatMessage, msg);
|
|
1473
1492
|
return msg;
|
|
1474
1493
|
}
|
|
@@ -1494,6 +1513,263 @@ export default class LocalParticipant extends Participant {
|
|
|
1494
1513
|
return msg;
|
|
1495
1514
|
}
|
|
1496
1515
|
|
|
1516
|
+
async sendText(text: string, options?: SendTextOptions): Promise<TextStreamInfo> {
|
|
1517
|
+
const streamId = crypto.randomUUID();
|
|
1518
|
+
const textInBytes = new TextEncoder().encode(text);
|
|
1519
|
+
const totalTextLength = textInBytes.byteLength;
|
|
1520
|
+
|
|
1521
|
+
const fileIds = options?.attachments?.map(() => crypto.randomUUID());
|
|
1522
|
+
|
|
1523
|
+
const progresses = new Array<number>(fileIds ? fileIds.length + 1 : 1).fill(0);
|
|
1524
|
+
|
|
1525
|
+
const handleProgress = (progress: number, idx: number) => {
|
|
1526
|
+
progresses[idx] = progress;
|
|
1527
|
+
const totalProgress = progresses.reduce((acc, val) => acc + val, 0);
|
|
1528
|
+
options?.onProgress?.(totalProgress);
|
|
1529
|
+
};
|
|
1530
|
+
|
|
1531
|
+
const writer = await this.streamText({
|
|
1532
|
+
streamId,
|
|
1533
|
+
totalSize: totalTextLength,
|
|
1534
|
+
destinationIdentities: options?.destinationIdentities,
|
|
1535
|
+
topic: options?.topic,
|
|
1536
|
+
attachedStreamIds: fileIds,
|
|
1537
|
+
});
|
|
1538
|
+
|
|
1539
|
+
const textChunkSize = Math.floor(STREAM_CHUNK_SIZE / 4); // utf8 is at most 4 bytes long, so play it safe and take a quarter of the byte size to slice the string
|
|
1540
|
+
const totalTextChunks = Math.ceil(totalTextLength / textChunkSize);
|
|
1541
|
+
|
|
1542
|
+
for (let i = 0; i < totalTextChunks; i++) {
|
|
1543
|
+
const chunkData = text.slice(
|
|
1544
|
+
i * textChunkSize,
|
|
1545
|
+
Math.min((i + 1) * textChunkSize, totalTextLength),
|
|
1546
|
+
);
|
|
1547
|
+
await this.engine.waitForBufferStatusLow(DataPacket_Kind.RELIABLE);
|
|
1548
|
+
await writer.write(chunkData);
|
|
1549
|
+
|
|
1550
|
+
handleProgress(Math.ceil((i + 1) / totalTextChunks), 0);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
await writer.close();
|
|
1554
|
+
|
|
1555
|
+
if (options?.attachments && fileIds) {
|
|
1556
|
+
await Promise.all(
|
|
1557
|
+
options.attachments.map(async (file, idx) =>
|
|
1558
|
+
this._sendFile(fileIds[idx], file, {
|
|
1559
|
+
topic: options.topic,
|
|
1560
|
+
mimeType: file.type,
|
|
1561
|
+
onProgress: (progress) => {
|
|
1562
|
+
handleProgress(progress, idx + 1);
|
|
1563
|
+
},
|
|
1564
|
+
}),
|
|
1565
|
+
),
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
return writer.info;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
/**
|
|
1572
|
+
* @internal
|
|
1573
|
+
* @experimental CAUTION, might get removed in a minor release
|
|
1574
|
+
*/
|
|
1575
|
+
async streamText(options?: StreamTextOptions): Promise<TextStreamWriter> {
|
|
1576
|
+
const streamId = options?.streamId ?? crypto.randomUUID();
|
|
1577
|
+
|
|
1578
|
+
const info: TextStreamInfo = {
|
|
1579
|
+
id: streamId,
|
|
1580
|
+
mimeType: 'text/plain',
|
|
1581
|
+
timestamp: Date.now(),
|
|
1582
|
+
topic: options?.topic ?? '',
|
|
1583
|
+
size: options?.totalSize,
|
|
1584
|
+
};
|
|
1585
|
+
const header = new DataStream_Header({
|
|
1586
|
+
streamId,
|
|
1587
|
+
mimeType: info.mimeType,
|
|
1588
|
+
topic: info.topic,
|
|
1589
|
+
timestamp: numberToBigInt(info.timestamp),
|
|
1590
|
+
totalLength: numberToBigInt(options?.totalSize),
|
|
1591
|
+
contentHeader: {
|
|
1592
|
+
case: 'textHeader',
|
|
1593
|
+
value: new DataStream_TextHeader({
|
|
1594
|
+
version: options?.version,
|
|
1595
|
+
attachedStreamIds: options?.attachedStreamIds,
|
|
1596
|
+
replyToStreamId: options?.replyToStreamId,
|
|
1597
|
+
operationType:
|
|
1598
|
+
options?.type === 'update'
|
|
1599
|
+
? DataStream_OperationType.UPDATE
|
|
1600
|
+
: DataStream_OperationType.CREATE,
|
|
1601
|
+
}),
|
|
1602
|
+
},
|
|
1603
|
+
});
|
|
1604
|
+
const destinationIdentities = options?.destinationIdentities;
|
|
1605
|
+
const packet = new DataPacket({
|
|
1606
|
+
destinationIdentities,
|
|
1607
|
+
value: {
|
|
1608
|
+
case: 'streamHeader',
|
|
1609
|
+
value: header,
|
|
1610
|
+
},
|
|
1611
|
+
});
|
|
1612
|
+
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
|
1613
|
+
|
|
1614
|
+
let chunkId = 0;
|
|
1615
|
+
const localP = this;
|
|
1616
|
+
|
|
1617
|
+
const writableStream = new WritableStream<[string, number?]>({
|
|
1618
|
+
// Implement the sink
|
|
1619
|
+
write([textChunk]) {
|
|
1620
|
+
const textInBytes = new TextEncoder().encode(textChunk);
|
|
1621
|
+
|
|
1622
|
+
if (textInBytes.byteLength > STREAM_CHUNK_SIZE) {
|
|
1623
|
+
this.abort?.();
|
|
1624
|
+
throw new Error('chunk size too large');
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
return new Promise(async (resolve) => {
|
|
1628
|
+
await localP.engine.waitForBufferStatusLow(DataPacket_Kind.RELIABLE);
|
|
1629
|
+
const chunk = new DataStream_Chunk({
|
|
1630
|
+
content: textInBytes,
|
|
1631
|
+
streamId,
|
|
1632
|
+
chunkIndex: numberToBigInt(chunkId),
|
|
1633
|
+
});
|
|
1634
|
+
const chunkPacket = new DataPacket({
|
|
1635
|
+
destinationIdentities,
|
|
1636
|
+
value: {
|
|
1637
|
+
case: 'streamChunk',
|
|
1638
|
+
value: chunk,
|
|
1639
|
+
},
|
|
1640
|
+
});
|
|
1641
|
+
await localP.engine.sendDataPacket(chunkPacket, DataPacket_Kind.RELIABLE);
|
|
1642
|
+
|
|
1643
|
+
chunkId += 1;
|
|
1644
|
+
resolve();
|
|
1645
|
+
});
|
|
1646
|
+
},
|
|
1647
|
+
async close() {
|
|
1648
|
+
const trailer = new DataStream_Trailer({
|
|
1649
|
+
streamId,
|
|
1650
|
+
});
|
|
1651
|
+
const trailerPacket = new DataPacket({
|
|
1652
|
+
destinationIdentities,
|
|
1653
|
+
value: {
|
|
1654
|
+
case: 'streamTrailer',
|
|
1655
|
+
value: trailer,
|
|
1656
|
+
},
|
|
1657
|
+
});
|
|
1658
|
+
await localP.engine.sendDataPacket(trailerPacket, DataPacket_Kind.RELIABLE);
|
|
1659
|
+
},
|
|
1660
|
+
abort(err) {
|
|
1661
|
+
console.log('Sink error:', err);
|
|
1662
|
+
// TODO handle aborts to signal something to receiver side
|
|
1663
|
+
},
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
let onEngineClose = async () => {
|
|
1667
|
+
await writer.close();
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
localP.engine.once(EngineEvent.Closing, onEngineClose);
|
|
1671
|
+
|
|
1672
|
+
const writer = new TextStreamWriter(writableStream, info, () =>
|
|
1673
|
+
this.engine.off(EngineEvent.Closing, onEngineClose),
|
|
1674
|
+
);
|
|
1675
|
+
|
|
1676
|
+
return writer;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
async sendFile(
|
|
1680
|
+
file: File,
|
|
1681
|
+
options?: {
|
|
1682
|
+
mimeType?: string;
|
|
1683
|
+
topic?: string;
|
|
1684
|
+
destinationIdentities?: Array<string>;
|
|
1685
|
+
onProgress?: (progress: number) => void;
|
|
1686
|
+
},
|
|
1687
|
+
): Promise<{ id: string }> {
|
|
1688
|
+
const streamId = crypto.randomUUID();
|
|
1689
|
+
await this._sendFile(streamId, file, options);
|
|
1690
|
+
return { id: streamId };
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
private async _sendFile(
|
|
1694
|
+
streamId: string,
|
|
1695
|
+
file: File,
|
|
1696
|
+
options?: {
|
|
1697
|
+
mimeType?: string;
|
|
1698
|
+
topic?: string;
|
|
1699
|
+
encryptionType?: Encryption_Type.NONE;
|
|
1700
|
+
destinationIdentities?: Array<string>;
|
|
1701
|
+
onProgress?: (progress: number) => void;
|
|
1702
|
+
},
|
|
1703
|
+
) {
|
|
1704
|
+
const totalLength = file.size;
|
|
1705
|
+
const header = new DataStream_Header({
|
|
1706
|
+
totalLength: numberToBigInt(totalLength),
|
|
1707
|
+
mimeType: options?.mimeType ?? file.type,
|
|
1708
|
+
streamId,
|
|
1709
|
+
topic: options?.topic,
|
|
1710
|
+
encryptionType: options?.encryptionType,
|
|
1711
|
+
timestamp: numberToBigInt(Date.now()),
|
|
1712
|
+
contentHeader: {
|
|
1713
|
+
case: 'byteHeader',
|
|
1714
|
+
value: new DataStream_ByteHeader({
|
|
1715
|
+
name: file.name,
|
|
1716
|
+
}),
|
|
1717
|
+
},
|
|
1718
|
+
});
|
|
1719
|
+
|
|
1720
|
+
const destinationIdentities = options?.destinationIdentities;
|
|
1721
|
+
const packet = new DataPacket({
|
|
1722
|
+
destinationIdentities,
|
|
1723
|
+
value: {
|
|
1724
|
+
case: 'streamHeader',
|
|
1725
|
+
value: header,
|
|
1726
|
+
},
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
|
1730
|
+
function read(b: Blob): Promise<Uint8Array> {
|
|
1731
|
+
return new Promise((resolve) => {
|
|
1732
|
+
const fr = new FileReader();
|
|
1733
|
+
fr.onload = () => {
|
|
1734
|
+
resolve(new Uint8Array(fr.result as ArrayBuffer));
|
|
1735
|
+
};
|
|
1736
|
+
fr.readAsArrayBuffer(b);
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
const totalChunks = Math.ceil(totalLength / STREAM_CHUNK_SIZE);
|
|
1740
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
1741
|
+
const chunkData = await read(
|
|
1742
|
+
file.slice(i * STREAM_CHUNK_SIZE, Math.min((i + 1) * STREAM_CHUNK_SIZE, totalLength)),
|
|
1743
|
+
);
|
|
1744
|
+
await this.engine.waitForBufferStatusLow(DataPacket_Kind.RELIABLE);
|
|
1745
|
+
const chunk = new DataStream_Chunk({
|
|
1746
|
+
content: chunkData,
|
|
1747
|
+
streamId,
|
|
1748
|
+
chunkIndex: numberToBigInt(i),
|
|
1749
|
+
});
|
|
1750
|
+
const chunkPacket = new DataPacket({
|
|
1751
|
+
destinationIdentities,
|
|
1752
|
+
value: {
|
|
1753
|
+
case: 'streamChunk',
|
|
1754
|
+
value: chunk,
|
|
1755
|
+
},
|
|
1756
|
+
});
|
|
1757
|
+
await this.engine.sendDataPacket(chunkPacket, DataPacket_Kind.RELIABLE);
|
|
1758
|
+
options?.onProgress?.((i + 1) / totalChunks);
|
|
1759
|
+
}
|
|
1760
|
+
const trailer = new DataStream_Trailer({
|
|
1761
|
+
streamId,
|
|
1762
|
+
});
|
|
1763
|
+
const trailerPacket = new DataPacket({
|
|
1764
|
+
destinationIdentities,
|
|
1765
|
+
value: {
|
|
1766
|
+
case: 'streamTrailer',
|
|
1767
|
+
value: trailer,
|
|
1768
|
+
},
|
|
1769
|
+
});
|
|
1770
|
+
await this.engine.sendDataPacket(trailerPacket, DataPacket_Kind.RELIABLE);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1497
1773
|
/**
|
|
1498
1774
|
* Initiate an RPC call to a remote participant
|
|
1499
1775
|
* @param params - Parameters for initiating the RPC call, see {@link PerformRpcParams}
|
|
@@ -1571,39 +1847,20 @@ export default class LocalParticipant extends Participant {
|
|
|
1571
1847
|
}
|
|
1572
1848
|
|
|
1573
1849
|
/**
|
|
1574
|
-
*
|
|
1575
|
-
* Will overwrite any existing callback for the same method.
|
|
1576
|
-
*
|
|
1577
|
-
* @param method - The name of the indicated RPC method
|
|
1578
|
-
* @param handler - Will be invoked when an RPC request for this method is received
|
|
1579
|
-
* @returns A promise that resolves when the method is successfully registered
|
|
1580
|
-
*
|
|
1581
|
-
* @example
|
|
1582
|
-
* ```typescript
|
|
1583
|
-
* room.localParticipant?.registerRpcMethod(
|
|
1584
|
-
* 'greet',
|
|
1585
|
-
* async (data: RpcInvocationData) => {
|
|
1586
|
-
* console.log(`Received greeting from ${data.callerIdentity}: ${data.payload}`);
|
|
1587
|
-
* return `Hello, ${data.callerIdentity}!`;
|
|
1588
|
-
* }
|
|
1589
|
-
* );
|
|
1590
|
-
* ```
|
|
1591
|
-
*
|
|
1592
|
-
* The handler should return a Promise that resolves to a string.
|
|
1593
|
-
* If unable to respond within `responseTimeout`, the request will result in an error on the caller's side.
|
|
1594
|
-
*
|
|
1595
|
-
* You may throw errors of type `RpcError` with a string `message` in the handler,
|
|
1596
|
-
* and they will be received on the caller's side with the message intact.
|
|
1597
|
-
* Other errors thrown in your handler will not be transmitted as-is, and will instead arrive to the caller as `1500` ("Application Error").
|
|
1850
|
+
* @deprecated use `room.registerRpcMethod` instead
|
|
1598
1851
|
*/
|
|
1599
1852
|
registerRpcMethod(method: string, handler: (data: RpcInvocationData) => Promise<string>) {
|
|
1853
|
+
if (this.rpcHandlers.has(method)) {
|
|
1854
|
+
this.log.warn(
|
|
1855
|
+
`you're overriding the RPC handler for method ${method}, in the future this will throw an error`,
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1600
1859
|
this.rpcHandlers.set(method, handler);
|
|
1601
1860
|
}
|
|
1602
1861
|
|
|
1603
1862
|
/**
|
|
1604
|
-
*
|
|
1605
|
-
*
|
|
1606
|
-
* @param method - The name of the RPC method to unregister
|
|
1863
|
+
* @deprecated use `room.unregisterRpcMethod` instead
|
|
1607
1864
|
*/
|
|
1608
1865
|
unregisterRpcMethod(method: string) {
|
|
1609
1866
|
this.rpcHandlers.delete(method);
|
|
@@ -1661,68 +1918,6 @@ export default class LocalParticipant extends Participant {
|
|
|
1661
1918
|
}
|
|
1662
1919
|
}
|
|
1663
1920
|
|
|
1664
|
-
private async handleIncomingRpcRequest(
|
|
1665
|
-
callerIdentity: string,
|
|
1666
|
-
requestId: string,
|
|
1667
|
-
method: string,
|
|
1668
|
-
payload: string,
|
|
1669
|
-
responseTimeout: number,
|
|
1670
|
-
version: number,
|
|
1671
|
-
) {
|
|
1672
|
-
await this.publishRpcAck(callerIdentity, requestId);
|
|
1673
|
-
|
|
1674
|
-
if (version !== 1) {
|
|
1675
|
-
await this.publishRpcResponse(
|
|
1676
|
-
callerIdentity,
|
|
1677
|
-
requestId,
|
|
1678
|
-
null,
|
|
1679
|
-
RpcError.builtIn('UNSUPPORTED_VERSION'),
|
|
1680
|
-
);
|
|
1681
|
-
return;
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
const handler = this.rpcHandlers.get(method);
|
|
1685
|
-
|
|
1686
|
-
if (!handler) {
|
|
1687
|
-
await this.publishRpcResponse(
|
|
1688
|
-
callerIdentity,
|
|
1689
|
-
requestId,
|
|
1690
|
-
null,
|
|
1691
|
-
RpcError.builtIn('UNSUPPORTED_METHOD'),
|
|
1692
|
-
);
|
|
1693
|
-
return;
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
let responseError: RpcError | null = null;
|
|
1697
|
-
let responsePayload: string | null = null;
|
|
1698
|
-
|
|
1699
|
-
try {
|
|
1700
|
-
const response = await handler({
|
|
1701
|
-
requestId,
|
|
1702
|
-
callerIdentity,
|
|
1703
|
-
payload,
|
|
1704
|
-
responseTimeout,
|
|
1705
|
-
});
|
|
1706
|
-
if (byteLength(response) > MAX_PAYLOAD_BYTES) {
|
|
1707
|
-
responseError = RpcError.builtIn('RESPONSE_PAYLOAD_TOO_LARGE');
|
|
1708
|
-
console.warn(`RPC Response payload too large for ${method}`);
|
|
1709
|
-
} else {
|
|
1710
|
-
responsePayload = response;
|
|
1711
|
-
}
|
|
1712
|
-
} catch (error) {
|
|
1713
|
-
if (error instanceof RpcError) {
|
|
1714
|
-
responseError = error;
|
|
1715
|
-
} else {
|
|
1716
|
-
console.warn(
|
|
1717
|
-
`Uncaught error returned by RPC handler for ${method}. Returning APPLICATION_ERROR instead.`,
|
|
1718
|
-
error,
|
|
1719
|
-
);
|
|
1720
|
-
responseError = RpcError.builtIn('APPLICATION_ERROR');
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
await this.publishRpcResponse(callerIdentity, requestId, responsePayload, responseError);
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
1921
|
/** @internal */
|
|
1727
1922
|
private async publishRpcRequest(
|
|
1728
1923
|
destinationIdentity: string,
|
|
@@ -1749,46 +1944,6 @@ export default class LocalParticipant extends Participant {
|
|
|
1749
1944
|
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
|
1750
1945
|
}
|
|
1751
1946
|
|
|
1752
|
-
/** @internal */
|
|
1753
|
-
private async publishRpcResponse(
|
|
1754
|
-
destinationIdentity: string,
|
|
1755
|
-
requestId: string,
|
|
1756
|
-
payload: string | null,
|
|
1757
|
-
error: RpcError | null,
|
|
1758
|
-
) {
|
|
1759
|
-
const packet = new DataPacket({
|
|
1760
|
-
destinationIdentities: [destinationIdentity],
|
|
1761
|
-
kind: DataPacket_Kind.RELIABLE,
|
|
1762
|
-
value: {
|
|
1763
|
-
case: 'rpcResponse',
|
|
1764
|
-
value: new RpcResponse({
|
|
1765
|
-
requestId,
|
|
1766
|
-
value: error
|
|
1767
|
-
? { case: 'error', value: error.toProto() }
|
|
1768
|
-
: { case: 'payload', value: payload ?? '' },
|
|
1769
|
-
}),
|
|
1770
|
-
},
|
|
1771
|
-
});
|
|
1772
|
-
|
|
1773
|
-
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
/** @internal */
|
|
1777
|
-
private async publishRpcAck(destinationIdentity: string, requestId: string) {
|
|
1778
|
-
const packet = new DataPacket({
|
|
1779
|
-
destinationIdentities: [destinationIdentity],
|
|
1780
|
-
kind: DataPacket_Kind.RELIABLE,
|
|
1781
|
-
value: {
|
|
1782
|
-
case: 'rpcAck',
|
|
1783
|
-
value: new RpcAck({
|
|
1784
|
-
requestId,
|
|
1785
|
-
}),
|
|
1786
|
-
},
|
|
1787
|
-
});
|
|
1788
|
-
|
|
1789
|
-
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
1947
|
/** @internal */
|
|
1793
1948
|
handleParticipantDisconnected(participantIdentity: string) {
|
|
1794
1949
|
for (const [id, { participantIdentity: pendingIdentity }] of this.pendingAcks) {
|
|
@@ -1962,7 +2117,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1962
2117
|
this.unpublishTrack(track);
|
|
1963
2118
|
} else if (track.isUserProvided) {
|
|
1964
2119
|
await track.mute();
|
|
1965
|
-
} else if (track
|
|
2120
|
+
} else if (isLocalAudioTrack(track) || isLocalVideoTrack(track)) {
|
|
1966
2121
|
try {
|
|
1967
2122
|
if (isWeb()) {
|
|
1968
2123
|
try {
|
|
@@ -1997,7 +2152,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1997
2152
|
...this.logContext,
|
|
1998
2153
|
...getLogContextFromTrack(track),
|
|
1999
2154
|
});
|
|
2000
|
-
if (track
|
|
2155
|
+
if (isLocalAudioTrack(track)) {
|
|
2001
2156
|
// fall back to default device if available
|
|
2002
2157
|
await track.restartTrack({ deviceId: 'default' });
|
|
2003
2158
|
} else {
|
|
@@ -2026,7 +2181,7 @@ export default class LocalParticipant extends Participant {
|
|
|
2026
2181
|
|
|
2027
2182
|
// this looks overly complicated due to this object tree
|
|
2028
2183
|
if (track instanceof MediaStreamTrack) {
|
|
2029
|
-
if (localTrack
|
|
2184
|
+
if (isLocalAudioTrack(localTrack) || isLocalVideoTrack(localTrack)) {
|
|
2030
2185
|
if (localTrack.mediaStreamTrack === track) {
|
|
2031
2186
|
publication = <LocalTrackPublication>pub;
|
|
2032
2187
|
}
|