@livekit/rtc-node 0.10.4 → 0.11.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.
Files changed (136) hide show
  1. package/README.md +54 -2
  2. package/build.rs +1 -1
  3. package/dist/audio_frame.d.ts +1 -1
  4. package/dist/audio_frame.d.ts.map +1 -1
  5. package/dist/audio_frame.js +4 -4
  6. package/dist/audio_frame.js.map +1 -1
  7. package/dist/audio_resampler.d.ts.map +1 -1
  8. package/dist/audio_resampler.js +8 -7
  9. package/dist/audio_resampler.js.map +1 -1
  10. package/dist/audio_source.d.ts.map +1 -1
  11. package/dist/audio_source.js +8 -4
  12. package/dist/audio_source.js.map +1 -1
  13. package/dist/audio_stream.d.ts +1 -1
  14. package/dist/audio_stream.d.ts.map +1 -1
  15. package/dist/audio_stream.js +4 -3
  16. package/dist/audio_stream.js.map +1 -1
  17. package/dist/e2ee.d.ts.map +1 -1
  18. package/dist/e2ee.js +25 -25
  19. package/dist/e2ee.js.map +1 -1
  20. package/dist/ffi_client.d.ts +3 -3
  21. package/dist/ffi_client.d.ts.map +1 -1
  22. package/dist/ffi_client.js +9 -7
  23. package/dist/ffi_client.js.map +1 -1
  24. package/dist/index.d.ts +29 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +34 -4
  27. package/dist/index.js.map +1 -1
  28. package/dist/napi/native.d.ts +1 -0
  29. package/dist/participant.d.ts +48 -2
  30. package/dist/participant.d.ts.map +1 -1
  31. package/dist/participant.js +143 -24
  32. package/dist/participant.js.map +1 -1
  33. package/dist/proto/audio_frame_pb.d.ts +451 -526
  34. package/dist/proto/audio_frame_pb.d.ts.map +1 -1
  35. package/dist/proto/audio_frame_pb.js +200 -1179
  36. package/dist/proto/audio_frame_pb.js.map +1 -1
  37. package/dist/proto/e2ee_pb.d.ts +264 -333
  38. package/dist/proto/e2ee_pb.d.ts.map +1 -1
  39. package/dist/proto/e2ee_pb.js +136 -810
  40. package/dist/proto/e2ee_pb.js.map +1 -1
  41. package/dist/proto/ffi_pb.d.ts +175 -129
  42. package/dist/proto/ffi_pb.d.ts.map +1 -1
  43. package/dist/proto/ffi_pb.js +58 -395
  44. package/dist/proto/ffi_pb.js.map +1 -1
  45. package/dist/proto/handle_pb.d.ts +14 -13
  46. package/dist/proto/handle_pb.d.ts.map +1 -1
  47. package/dist/proto/handle_pb.js +8 -39
  48. package/dist/proto/handle_pb.js.map +1 -1
  49. package/dist/proto/participant_pb.d.ts +54 -52
  50. package/dist/proto/participant_pb.d.ts.map +1 -1
  51. package/dist/proto/participant_pb.js +18 -90
  52. package/dist/proto/participant_pb.js.map +1 -1
  53. package/dist/proto/room_pb.d.ts +1042 -1187
  54. package/dist/proto/room_pb.d.ts.map +1 -1
  55. package/dist/proto/room_pb.js +470 -2938
  56. package/dist/proto/room_pb.js.map +1 -1
  57. package/dist/proto/rpc_pb.d.ts +235 -0
  58. package/dist/proto/rpc_pb.d.ts.map +1 -0
  59. package/dist/proto/rpc_pb.js +74 -0
  60. package/dist/proto/rpc_pb.js.map +1 -0
  61. package/dist/proto/stats_pb.d.ts +738 -800
  62. package/dist/proto/stats_pb.d.ts.map +1 -1
  63. package/dist/proto/stats_pb.js +218 -1960
  64. package/dist/proto/stats_pb.js.map +1 -1
  65. package/dist/proto/track_pb.d.ts +225 -258
  66. package/dist/proto/track_pb.d.ts.map +1 -1
  67. package/dist/proto/track_pb.js +98 -591
  68. package/dist/proto/track_pb.js.map +1 -1
  69. package/dist/proto/video_frame_pb.d.ts +357 -390
  70. package/dist/proto/video_frame_pb.d.ts.map +1 -1
  71. package/dist/proto/video_frame_pb.js +137 -790
  72. package/dist/proto/video_frame_pb.js.map +1 -1
  73. package/dist/room.d.ts +21 -7
  74. package/dist/room.d.ts.map +1 -1
  75. package/dist/room.js +134 -54
  76. package/dist/room.js.map +1 -1
  77. package/dist/rpc.d.ts +78 -0
  78. package/dist/rpc.d.ts.map +1 -0
  79. package/dist/rpc.js +69 -0
  80. package/dist/rpc.js.map +1 -0
  81. package/dist/track.d.ts.map +1 -1
  82. package/dist/track.js +7 -3
  83. package/dist/track.js.map +1 -1
  84. package/dist/track_publication.d.ts.map +1 -1
  85. package/dist/track_publication.js +3 -2
  86. package/dist/track_publication.js.map +1 -1
  87. package/dist/version.d.ts +2 -0
  88. package/dist/version.d.ts.map +1 -0
  89. package/dist/version.js +2 -0
  90. package/dist/version.js.map +1 -0
  91. package/dist/video_frame.d.ts +2 -2
  92. package/dist/video_frame.d.ts.map +1 -1
  93. package/dist/video_frame.js +47 -25
  94. package/dist/video_frame.js.map +1 -1
  95. package/dist/video_source.d.ts.map +1 -1
  96. package/dist/video_source.js +4 -3
  97. package/dist/video_source.js.map +1 -1
  98. package/dist/video_stream.d.ts +1 -1
  99. package/dist/video_stream.d.ts.map +1 -1
  100. package/dist/video_stream.js +7 -3
  101. package/dist/video_stream.js.map +1 -1
  102. package/package.json +13 -10
  103. package/src/audio_frame.ts +6 -6
  104. package/src/audio_resampler.ts +10 -9
  105. package/src/audio_source.ts +9 -8
  106. package/src/audio_stream.ts +7 -6
  107. package/src/e2ee.ts +34 -34
  108. package/src/ffi_client.ts +23 -10
  109. package/src/index.ts +61 -2
  110. package/src/napi/native.d.ts +1 -0
  111. package/src/nodejs.rs +6 -3
  112. package/src/participant.ts +207 -47
  113. package/src/proto/audio_frame_pb.ts +562 -1279
  114. package/src/proto/e2ee_pb.ts +365 -886
  115. package/src/proto/ffi_pb.ts +206 -405
  116. package/src/proto/handle_pb.ts +21 -35
  117. package/src/proto/participant_pb.ts +71 -112
  118. package/src/proto/room_pb.ts +1284 -2995
  119. package/src/proto/rpc_pb.ts +310 -0
  120. package/src/proto/stats_pb.ts +1028 -1947
  121. package/src/proto/track_pb.ts +287 -621
  122. package/src/proto/video_frame_pb.ts +445 -905
  123. package/src/room.ts +181 -75
  124. package/src/rpc.ts +121 -0
  125. package/src/track.ts +8 -7
  126. package/src/track_publication.ts +5 -4
  127. package/src/version.ts +1 -0
  128. package/src/video_frame.ts +54 -27
  129. package/src/video_source.ts +7 -6
  130. package/src/video_stream.ts +7 -6
  131. package/dist/utils.d.ts +0 -7
  132. package/dist/utils.d.ts.map +0 -1
  133. package/dist/utils.js +0 -48
  134. package/dist/utils.js.map +0 -1
  135. package/src/utils.test.ts +0 -47
  136. package/src/utils.ts +0 -44
package/src/room.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
+ import { create } from '@bufbuild/protobuf';
4
5
  import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
5
6
  import EventEmitter from 'events';
6
7
  import type { E2EEOptions } from './e2ee.js';
@@ -18,11 +19,12 @@ import type {
18
19
  DataPacketKind,
19
20
  DisconnectReason,
20
21
  DisconnectResponse,
22
+ RoomOptions as FfiRoomOptions,
21
23
  IceServer,
22
24
  RoomInfo,
23
25
  } from './proto/room_pb.js';
24
26
  import {
25
- ConnectRequest,
27
+ ConnectRequestSchema,
26
28
  ConnectionState,
27
29
  ContinualGatheringPolicy,
28
30
  IceTransportType,
@@ -53,18 +55,20 @@ export interface RoomOptions {
53
55
  rtcConfig?: RtcConfiguration;
54
56
  }
55
57
 
56
- export const defaultRoomOptions: RoomOptions = {
58
+ export const defaultRoomOptions = {
57
59
  autoSubscribe: true,
58
60
  dynacast: false,
59
61
  e2ee: undefined,
60
62
  rtcConfig: undefined,
61
- };
63
+ adaptiveStream: false,
64
+ joinRetries: 1,
65
+ } satisfies Omit<FfiRoomOptions, '$typeName'>;
62
66
 
63
67
  export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>) {
64
- private info: RoomInfo;
68
+ private info?: RoomInfo;
65
69
  private ffiHandle?: FfiHandle;
66
70
 
67
- e2eeManager: E2EEManager;
71
+ e2eeManager?: E2EEManager;
68
72
  connectionState: ConnectionState = ConnectionState.CONN_DISCONNECTED;
69
73
 
70
74
  remoteParticipants: Map<string, RemoteParticipant> = new Map();
@@ -74,12 +78,12 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
74
78
  super();
75
79
  }
76
80
 
77
- get name(): string {
78
- return this.info.name;
81
+ get name(): string | undefined {
82
+ return this.info?.name;
79
83
  }
80
84
 
81
- get metadata(): string {
82
- return this.info.metadata;
85
+ get metadata(): string | undefined {
86
+ return this.info?.metadata;
83
87
  }
84
88
 
85
89
  get isConnected(): boolean {
@@ -87,28 +91,34 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
87
91
  }
88
92
 
89
93
  async getSid(): Promise<string> {
90
- return this.info.sid; // TODO update this to handle async room updates once rust protocol has been updated
94
+ if (!this.isConnected) {
95
+ return '';
96
+ }
97
+ if (this.info && this.info.sid !== '') {
98
+ return this.info.sid;
99
+ }
100
+ return new Promise((resolve, reject) => {
101
+ const handleRoomUpdate = (sid: string) => {
102
+ if (sid !== '') {
103
+ this.off(RoomEvent.RoomSidChanged, handleRoomUpdate);
104
+ resolve(sid);
105
+ }
106
+ };
107
+ this.on(RoomEvent.RoomSidChanged, handleRoomUpdate);
108
+ this.once(RoomEvent.Disconnected, () => {
109
+ this.off(RoomEvent.RoomSidChanged, handleRoomUpdate);
110
+ reject('Room disconnected before room server id was available');
111
+ });
112
+ });
91
113
  }
92
114
 
93
115
  async connect(url: string, token: string, opts?: RoomOptions) {
94
116
  const options = { ...defaultRoomOptions, ...opts };
95
117
 
96
- const req = new ConnectRequest({
118
+ const req = create(ConnectRequestSchema, {
97
119
  url: url,
98
120
  token: token,
99
- options: {
100
- autoSubscribe: options.autoSubscribe,
101
- dynacast: options.dynacast,
102
- e2ee: {
103
- encryptionType: options.e2ee?.encryptionType,
104
- keyProviderOptions: {
105
- failureTolerance: options.e2ee?.keyProviderOptions?.failureTolerance,
106
- ratchetSalt: options.e2ee?.keyProviderOptions?.ratchetSalt,
107
- ratchetWindowSize: options.e2ee?.keyProviderOptions?.ratchetWindowSize,
108
- sharedKey: options.e2ee?.keyProviderOptions?.sharedKey,
109
- },
110
- },
111
- },
121
+ options,
112
122
  });
113
123
 
114
124
  const res = FfiClient.instance.request<ConnectResponse>({
@@ -122,19 +132,23 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
122
132
  return ev.message.case == 'connect' && ev.message.value.asyncId == res.asyncId;
123
133
  });
124
134
 
125
- if (cb.error) {
126
- throw new ConnectError(cb.error);
135
+ if (cb.message.case !== 'result') {
136
+ throw new ConnectError(cb.message.value ?? 'Unknown error');
127
137
  }
128
138
 
129
- this.ffiHandle = new FfiHandle(cb.room.handle.id);
130
- this.e2eeManager = new E2EEManager(this.ffiHandle.handle, options.e2ee);
139
+ const { room, localParticipant, participants } = cb.message.value;
140
+
141
+ this.ffiHandle = new FfiHandle(room!.handle!.id);
142
+ if (options.e2ee) {
143
+ this.e2eeManager = new E2EEManager(this.ffiHandle.handle, options.e2ee);
144
+ }
131
145
 
132
- this.info = cb.room.info;
146
+ this.info = room!.info;
133
147
  this.connectionState = ConnectionState.CONN_CONNECTED;
134
- this.localParticipant = new LocalParticipant(cb.localParticipant);
148
+ this.localParticipant = new LocalParticipant(localParticipant!);
135
149
 
136
- for (const pt of cb.participants) {
137
- const rp = this.createRemoteParticipant(pt.participant);
150
+ for (const pt of participants) {
151
+ const rp = this.createRemoteParticipant(pt.participant!);
138
152
 
139
153
  for (const pub of pt.publications) {
140
154
  const publication = new RemoteTrackPublication(pub);
@@ -146,7 +160,7 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
146
160
  }
147
161
 
148
162
  async disconnect() {
149
- if (!this.isConnected) {
163
+ if (!this.isConnected || !this.ffiHandle) {
150
164
  return;
151
165
  }
152
166
 
@@ -164,7 +178,25 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
164
178
  }
165
179
 
166
180
  private onFfiEvent = (ffiEvent: FfiEvent) => {
167
- if (
181
+ if (!this.localParticipant || !this.ffiHandle || !this.info) {
182
+ throw TypeError('cannot handle ffi events before connectCallback');
183
+ }
184
+
185
+ if (ffiEvent.message.case == 'rpcMethodInvocation') {
186
+ if (
187
+ ffiEvent.message.value.localParticipantHandle == this.localParticipant.ffi_handle.handle
188
+ ) {
189
+ this.localParticipant.handleRpcMethodInvocation(
190
+ ffiEvent.message.value.invocationId,
191
+ ffiEvent.message.value.method,
192
+ ffiEvent.message.value.requestId,
193
+ ffiEvent.message.value.callerIdentity,
194
+ ffiEvent.message.value.payload,
195
+ ffiEvent.message.value.responseTimeoutMs,
196
+ );
197
+ }
198
+ return;
199
+ } else if (
168
200
  ffiEvent.message.case != 'roomEvent' ||
169
201
  ffiEvent.message.value.roomHandle != this.ffiHandle.handle
170
202
  ) {
@@ -173,66 +205,88 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
173
205
 
174
206
  const ev = ffiEvent.message.value.message;
175
207
  if (ev.case == 'participantConnected') {
176
- const participant = this.createRemoteParticipant(ev.value.info);
208
+ const participant = this.createRemoteParticipant(ev.value.info!);
177
209
  this.remoteParticipants.set(participant.identity, participant);
178
210
  this.emit(RoomEvent.ParticipantConnected, participant);
179
211
  } else if (ev.case == 'participantDisconnected') {
180
212
  const participant = this.remoteParticipants.get(ev.value.participantIdentity);
181
- this.remoteParticipants.delete(participant.identity);
182
- this.emit(RoomEvent.ParticipantDisconnected, participant);
213
+ if (participant) {
214
+ this.remoteParticipants.delete(participant.identity);
215
+ this.emit(RoomEvent.ParticipantDisconnected, participant);
216
+ }
183
217
  } else if (ev.case == 'localTrackPublished') {
184
218
  const publication = this.localParticipant.trackPublications.get(ev.value.trackSid);
219
+ if (!publication) {
220
+ throw new TypeError('local track publication not found');
221
+ }
185
222
  this.emit(RoomEvent.LocalTrackPublished, publication, this.localParticipant);
186
223
  } else if (ev.case == 'localTrackUnpublished') {
187
224
  const publication = this.localParticipant.trackPublications.get(ev.value.publicationSid);
188
225
  this.localParticipant.trackPublications.delete(ev.value.publicationSid);
189
- this.emit(RoomEvent.LocalTrackUnpublished, publication, this.localParticipant);
226
+ if (publication) {
227
+ this.emit(RoomEvent.LocalTrackUnpublished, publication, this.localParticipant);
228
+ }
190
229
  } else if (ev.case == 'localTrackSubscribed') {
191
230
  const publication = this.localParticipant.trackPublications.get(ev.value.trackSid);
231
+ if (!publication) {
232
+ throw new TypeError('local track publication not found');
233
+ }
192
234
  publication.resolveFirstSubscription();
193
- this.emit(RoomEvent.LocalTrackSubscribed, publication.track);
235
+ this.emit(RoomEvent.LocalTrackSubscribed, publication.track!);
194
236
  } else if (ev.case == 'trackPublished') {
195
- const participant = this.remoteParticipants.get(ev.value.participantIdentity);
196
- const publication = new RemoteTrackPublication(ev.value.publication);
237
+ const participant = this.requireRemoteParticipant(ev.value.participantIdentity);
238
+ const publication = new RemoteTrackPublication(ev.value.publication!);
197
239
  participant.trackPublications.set(publication.sid, publication);
198
240
  this.emit(RoomEvent.TrackPublished, publication, participant);
199
241
  } else if (ev.case == 'trackUnpublished') {
200
- const participant = this.remoteParticipants.get(ev.value.participantIdentity);
242
+ const participant = this.requireRemoteParticipant(ev.value.participantIdentity);
201
243
  const publication = participant.trackPublications.get(ev.value.publicationSid);
202
244
  participant.trackPublications.delete(ev.value.publicationSid);
203
- this.emit(RoomEvent.TrackUnpublished, publication, participant);
245
+ if (publication) {
246
+ this.emit(RoomEvent.TrackUnpublished, publication, participant);
247
+ }
204
248
  } else if (ev.case == 'trackSubscribed') {
205
- const ownedTrack = ev.value.track;
206
- const participant = this.remoteParticipants.get(ev.value.participantIdentity);
207
- const publication = participant.trackPublications.get(ownedTrack.info.sid);
249
+ const ownedTrack = ev.value.track!;
250
+ const trackInfo = ownedTrack.info!;
251
+ const { participant, publication } = this.requirePublicationOfRemoteParticipant(
252
+ ev.value.participantIdentity,
253
+ trackInfo.sid,
254
+ );
208
255
  publication.subscribed = true;
209
- if (ownedTrack.info.kind == TrackKind.KIND_VIDEO) {
256
+ if (trackInfo.kind == TrackKind.KIND_VIDEO) {
210
257
  publication.track = new RemoteVideoTrack(ownedTrack);
211
- } else if (ownedTrack.info.kind == TrackKind.KIND_AUDIO) {
258
+ } else if (trackInfo.kind == TrackKind.KIND_AUDIO) {
212
259
  publication.track = new RemoteAudioTrack(ownedTrack);
213
260
  }
214
261
 
215
- this.emit(RoomEvent.TrackSubscribed, publication.track, publication, participant);
262
+ this.emit(RoomEvent.TrackSubscribed, publication.track!, publication, participant);
216
263
  } else if (ev.case == 'trackUnsubscribed') {
217
- const participant = this.remoteParticipants.get(ev.value.participantIdentity);
218
- const publication = participant.trackPublications.get(ev.value.trackSid);
264
+ const { participant, publication } = this.requirePublicationOfRemoteParticipant(
265
+ ev.value.participantIdentity,
266
+ ev.value.trackSid,
267
+ );
268
+ const track = publication.track!;
219
269
  publication.track = undefined;
220
270
  publication.subscribed = false;
221
- this.emit(RoomEvent.TrackUnsubscribed, publication.track, publication, participant);
271
+ this.emit(RoomEvent.TrackUnsubscribed, track, publication, participant);
222
272
  } else if (ev.case == 'trackSubscriptionFailed') {
223
- const participant = this.remoteParticipants.get(ev.value.participantIdentity);
273
+ const participant = this.requireRemoteParticipant(ev.value.participantIdentity);
224
274
  this.emit(RoomEvent.TrackSubscriptionFailed, ev.value.trackSid, participant, ev.value.error);
225
275
  } else if (ev.case == 'trackMuted') {
226
- const participant = this.remoteParticipants.get(ev.value.participantIdentity);
227
- const publication = participant.trackPublications.get(ev.value.trackSid);
276
+ const { participant, publication } = this.requirePublicationOfParticipant(
277
+ ev.value.participantIdentity,
278
+ ev.value.trackSid,
279
+ );
228
280
  publication.info.muted = true;
229
281
  if (publication.track) {
230
282
  publication.track.info.muted = true;
231
283
  }
232
284
  this.emit(RoomEvent.TrackMuted, publication, participant);
233
285
  } else if (ev.case == 'trackUnmuted') {
234
- const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity);
235
- const publication = participant.trackPublications.get(ev.value.trackSid);
286
+ const { participant, publication } = this.requirePublicationOfParticipant(
287
+ ev.value.participantIdentity,
288
+ ev.value.trackSid,
289
+ );
236
290
  publication.info.muted = false;
237
291
  if (publication.track) {
238
292
  publication.track.info.muted = false;
@@ -240,32 +294,45 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
240
294
  this.emit(RoomEvent.TrackUnmuted, publication, participant);
241
295
  } else if (ev.case == 'activeSpeakersChanged') {
242
296
  const activeSpeakers = ev.value.participantIdentities.map((identity) =>
243
- this.retrieveParticipantByIdentity(identity),
297
+ this.requireParticipantByIdentity(identity),
244
298
  );
245
299
  this.emit(RoomEvent.ActiveSpeakersChanged, activeSpeakers);
246
300
  } else if (ev.case == 'roomMetadataChanged') {
247
301
  this.info.metadata = ev.value.metadata;
248
302
  this.emit(RoomEvent.RoomMetadataChanged, this.info.metadata);
249
303
  } else if (ev.case == 'participantMetadataChanged') {
250
- const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity);
304
+ const participant = this.requireParticipantByIdentity(ev.value.participantIdentity);
251
305
  participant.info.metadata = ev.value.metadata;
252
306
  this.emit(RoomEvent.ParticipantMetadataChanged, participant.metadata, participant);
253
307
  } else if (ev.case == 'participantNameChanged') {
254
- const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity);
308
+ const participant = this.requireParticipantByIdentity(ev.value.participantIdentity);
255
309
  participant.info.name = ev.value.name;
256
310
  this.emit(RoomEvent.ParticipantNameChanged, participant.name, participant);
257
311
  } else if (ev.case == 'participantAttributesChanged') {
258
- const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity);
259
- participant.info.attributes = ev.value.attributes;
312
+ const participant = this.requireParticipantByIdentity(ev.value.participantIdentity);
313
+ participant.info.attributes = ev.value.attributes.reduce(
314
+ (acc, value) => {
315
+ acc[value.key] = value.value;
316
+ return acc;
317
+ },
318
+ {} as Record<string, string>,
319
+ );
260
320
  if (Object.keys(ev.value.changedAttributes).length > 0) {
261
- this.emit(RoomEvent.ParticipantAttributesChanged, ev.value.changedAttributes, participant);
321
+ const changedAttributes = ev.value.changedAttributes.reduce(
322
+ (acc, value) => {
323
+ acc[value.key] = value.value;
324
+ return acc;
325
+ },
326
+ {} as Record<string, string>,
327
+ );
328
+ this.emit(RoomEvent.ParticipantAttributesChanged, changedAttributes, participant);
262
329
  }
263
330
  } else if (ev.case == 'connectionQualityChanged') {
264
- const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity);
331
+ const participant = this.requireParticipantByIdentity(ev.value.participantIdentity);
265
332
  this.emit(RoomEvent.ConnectionQualityChanged, ev.value.quality, participant);
266
333
  } else if (ev.case == 'chatMessage') {
267
334
  const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity);
268
- const { id, message: messageText, timestamp, editTimestamp, generated } = ev.value.message;
335
+ const { id, message: messageText, timestamp, editTimestamp, generated } = ev.value.message!;
269
336
  const message: ChatMessage = {
270
337
  id,
271
338
  message: messageText,
@@ -280,11 +347,9 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
280
347
  const dataPacket = ev.value.value;
281
348
  switch (dataPacket.case) {
282
349
  case 'user':
283
- const buffer = FfiClient.instance.copyBuffer(
284
- dataPacket.value.data.data.dataPtr,
285
- Number(dataPacket.value.data.data.dataLen),
286
- );
287
- new FfiHandle(dataPacket.value.data.handle.id).dispose();
350
+ const data = dataPacket.value.data!.data!;
351
+ const buffer = FfiClient.instance.copyBuffer(data.dataPtr, Number(data.dataLen));
352
+ new FfiHandle(dataPacket.value.data!.handle!.id).dispose();
288
353
  this.emit(
289
354
  RoomEvent.DataReceived,
290
355
  buffer,
@@ -316,24 +381,62 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
316
381
  this.emit(RoomEvent.Reconnecting);
317
382
  } else if (ev.case == 'reconnected') {
318
383
  this.emit(RoomEvent.Reconnected);
384
+ } else if (ev.case == 'roomSidChanged') {
385
+ this.emit(RoomEvent.RoomSidChanged, ev.value.sid);
319
386
  }
320
387
  };
321
388
 
322
- private retrieveParticipantByIdentity(identity: string): Participant {
323
- if (this.localParticipant.identity === identity) {
389
+ private retrieveParticipantByIdentity(identity: string): Participant | undefined {
390
+ if (this.localParticipant?.identity === identity) {
324
391
  return this.localParticipant;
325
392
  } else {
326
393
  return this.remoteParticipants.get(identity);
327
394
  }
328
395
  }
329
396
 
397
+ private requireParticipantByIdentity(identity: string): Participant {
398
+ if (this.localParticipant?.identity === identity) {
399
+ return this.localParticipant;
400
+ } else if (this.remoteParticipants.has(identity)) {
401
+ return this.remoteParticipants.get(identity)!;
402
+ } else {
403
+ throw new TypeError(`participant ${identity} not found`);
404
+ }
405
+ }
406
+
407
+ private requireRemoteParticipant(identity: string) {
408
+ const participant = this.remoteParticipants.get(identity);
409
+ if (!participant) {
410
+ throw new TypeError(`participant ${identity} not found`);
411
+ }
412
+ return participant;
413
+ }
414
+
415
+ private requirePublicationOfParticipant(identity: string, trackSid: string) {
416
+ const participant = this.requireParticipantByIdentity(identity);
417
+ const publication = participant.trackPublications.get(trackSid);
418
+ if (!publication) {
419
+ throw new TypeError(`publication ${trackSid} not found`);
420
+ }
421
+ return { participant, publication };
422
+ }
423
+
424
+ private requirePublicationOfRemoteParticipant(identity: string, trackSid: string) {
425
+ const participant = this.requireRemoteParticipant(identity);
426
+ const publication = participant.trackPublications.get(trackSid);
427
+ if (!publication) {
428
+ throw new TypeError(`publication ${trackSid} not found`);
429
+ }
430
+ return { participant, publication };
431
+ }
432
+
330
433
  private createRemoteParticipant(ownedInfo: OwnedParticipant) {
331
- if (this.remoteParticipants.has(ownedInfo.info.identity)) {
434
+ if (this.remoteParticipants.has(ownedInfo.info!.identity)) {
332
435
  throw new Error('Participant already exists');
333
436
  }
334
437
 
335
438
  const participant = new RemoteParticipant(ownedInfo);
336
- this.remoteParticipants.set(ownedInfo.info.identity, participant);
439
+ this.remoteParticipants.set(ownedInfo.info!.identity, participant);
337
440
  return participant;
338
441
  }
339
442
  }
@@ -374,6 +477,7 @@ export type RoomCallbacks = {
374
477
  trackUnmuted: (publication: TrackPublication, participant: Participant) => void;
375
478
  activeSpeakersChanged: (speakers: Participant[]) => void;
376
479
  roomMetadataChanged: (metadata: string) => void;
480
+ roomInfoUpdated: (info: RoomInfo) => void;
377
481
  participantMetadataChanged: (metadata: string | undefined, participant: Participant) => void;
378
482
  participantNameChanged: (name: string, participant: Participant) => void;
379
483
  participantAttributesChanged: (
@@ -388,13 +492,14 @@ export type RoomCallbacks = {
388
492
  topic?: string,
389
493
  ) => void;
390
494
  chatMessage: (message: ChatMessage, participant?: Participant) => void;
391
- dtmfReceived: (code: number, digit: string, participant: RemoteParticipant) => void;
495
+ dtmfReceived: (code: number, digit: string, participant?: RemoteParticipant) => void;
392
496
  encryptionError: (error: Error) => void;
393
497
  connectionStateChanged: (state: ConnectionState) => void;
394
498
  connected: () => void;
395
499
  disconnected: (reason: DisconnectReason) => void;
396
500
  reconnecting: () => void;
397
501
  reconnected: () => void;
502
+ roomSidChanged: (sid: string) => void;
398
503
  };
399
504
 
400
505
  export enum RoomEvent {
@@ -412,6 +517,7 @@ export enum RoomEvent {
412
517
  TrackUnmuted = 'trackUnmuted',
413
518
  ActiveSpeakersChanged = 'activeSpeakersChanged',
414
519
  RoomMetadataChanged = 'roomMetadataChanged',
520
+ RoomSidChanged = 'roomSidChanged',
415
521
  ParticipantMetadataChanged = 'participantMetadataChanged',
416
522
  ParticipantNameChanged = 'participantNameChanged',
417
523
  ParticipantAttributesChanged = 'participantAttributesChanged',
package/src/rpc.ts ADDED
@@ -0,0 +1,121 @@
1
+ // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ import type { RpcError as RpcError_Proto } from './proto/rpc_pb.js';
5
+
6
+ /** Parameters for initiating an RPC call */
7
+ export interface PerformRpcParams {
8
+ /** The `identity` of the destination participant */
9
+ destinationIdentity: string;
10
+ /** The method name to call */
11
+ method: string;
12
+ /** The method payload */
13
+ payload: string;
14
+ /** Timeout for receiving a response after initial connection (milliseconds). Default: 10000 */
15
+ responseTimeout?: number;
16
+ }
17
+
18
+ /**
19
+ * Data passed to method handler for incoming RPC invocations
20
+ */
21
+ export interface RpcInvocationData {
22
+ /**
23
+ * The unique request ID. Will match at both sides of the call, useful for debugging or logging.
24
+ */
25
+ requestId: string;
26
+
27
+ /**
28
+ * The unique participant identity of the caller.
29
+ */
30
+ callerIdentity: string;
31
+
32
+ /**
33
+ * The payload of the request. User-definable format, typically JSON.
34
+ */
35
+ payload: string;
36
+
37
+ /**
38
+ * The maximum time the caller will wait for a response.
39
+ */
40
+ responseTimeout: number;
41
+ }
42
+
43
+ /**
44
+ * Specialized error handling for RPC methods.
45
+ *
46
+ * Instances of this type, when thrown in a method handler, will have their `message`
47
+ * serialized and sent across the wire. The caller will receive an equivalent error on the other side.
48
+ *
49
+ * Built-in types are included but developers may use any string, with a max length of 256 bytes.
50
+ */
51
+
52
+ export class RpcError extends Error {
53
+ code: typeof RpcError.ErrorCode | number;
54
+ data?: string;
55
+
56
+ /**
57
+ * Creates an error object with the given code and message, plus an optional data payload.
58
+ *
59
+ * If thrown in an RPC method handler, the error will be sent back to the caller.
60
+ *
61
+ * Error codes 1001-1999 are reserved for built-in errors (see RpcError.ErrorCode for their meanings).
62
+ */
63
+ constructor(code: number, message: string, data?: string) {
64
+ super(message);
65
+ this.code = code;
66
+ this.message = message;
67
+ this.data = data;
68
+ }
69
+
70
+ static fromProto(proto: RpcError_Proto) {
71
+ return new RpcError(proto.code, proto.message, proto.data);
72
+ }
73
+
74
+ toProto() {
75
+ return {
76
+ code: this.code as number,
77
+ message: this.message,
78
+ data: this.data,
79
+ } as RpcError_Proto;
80
+ }
81
+
82
+ static ErrorCode = {
83
+ APPLICATION_ERROR: 1500,
84
+ CONNECTION_TIMEOUT: 1501,
85
+ RESPONSE_TIMEOUT: 1502,
86
+ RECIPIENT_DISCONNECTED: 1503,
87
+ RESPONSE_PAYLOAD_TOO_LARGE: 1504,
88
+ SEND_FAILED: 1505,
89
+
90
+ UNSUPPORTED_METHOD: 1400,
91
+ RECIPIENT_NOT_FOUND: 1401,
92
+ REQUEST_PAYLOAD_TOO_LARGE: 1402,
93
+ UNSUPPORTED_SERVER: 1403,
94
+ } as const;
95
+
96
+ /**
97
+ * @internal
98
+ */
99
+ static ErrorMessage: Record<keyof typeof RpcError.ErrorCode, string> = {
100
+ APPLICATION_ERROR: 'Application error in method handler',
101
+ CONNECTION_TIMEOUT: 'Connection timeout',
102
+ RESPONSE_TIMEOUT: 'Response timeout',
103
+ RECIPIENT_DISCONNECTED: 'Recipient disconnected',
104
+ RESPONSE_PAYLOAD_TOO_LARGE: 'Response payload too large',
105
+ SEND_FAILED: 'Failed to send',
106
+
107
+ UNSUPPORTED_METHOD: 'Method not supported at destination',
108
+ RECIPIENT_NOT_FOUND: 'Recipient not found',
109
+ REQUEST_PAYLOAD_TOO_LARGE: 'Request payload too large',
110
+ UNSUPPORTED_SERVER: 'RPC not supported by server',
111
+ } as const;
112
+
113
+ /**
114
+ * Creates an error object from the code, with an auto-populated message.
115
+ *
116
+ * @internal
117
+ */
118
+ static builtIn(key: keyof typeof RpcError.ErrorCode, data?: string): RpcError {
119
+ return new RpcError(RpcError.ErrorCode[key], RpcError.ErrorMessage[key], data);
120
+ }
121
+ }
package/src/track.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
+ import { create } from '@bufbuild/protobuf';
4
5
  import type { AudioSource } from './audio_source.js';
5
6
  import { FfiClient, FfiHandle } from './ffi_client.js';
6
7
  import type {
@@ -11,7 +12,7 @@ import type {
11
12
  TrackInfo,
12
13
  TrackKind,
13
14
  } from './proto/track_pb.js';
14
- import { CreateAudioTrackRequest, CreateVideoTrackRequest } from './proto/track_pb.js';
15
+ import { CreateAudioTrackRequestSchema, CreateVideoTrackRequestSchema } from './proto/track_pb.js';
15
16
  import type { VideoSource } from './video_source.js';
16
17
 
17
18
  export abstract class Track {
@@ -22,8 +23,8 @@ export abstract class Track {
22
23
  ffi_handle: FfiHandle;
23
24
 
24
25
  constructor(owned: OwnedTrack) {
25
- this.info = owned.info;
26
- this.ffi_handle = new FfiHandle(owned.handle.id);
26
+ this.info = owned.info!;
27
+ this.ffi_handle = new FfiHandle(owned.handle!.id);
27
28
  }
28
29
 
29
30
  get sid(): string {
@@ -53,7 +54,7 @@ export class LocalAudioTrack extends Track {
53
54
  }
54
55
 
55
56
  static createAudioTrack(name: string, source: AudioSource): LocalAudioTrack {
56
- const req = new CreateAudioTrackRequest({
57
+ const req = create(CreateAudioTrackRequestSchema, {
57
58
  name: name,
58
59
  sourceHandle: source.ffiHandle.handle,
59
60
  });
@@ -62,7 +63,7 @@ export class LocalAudioTrack extends Track {
62
63
  message: { case: 'createAudioTrack', value: req },
63
64
  });
64
65
 
65
- return new LocalAudioTrack(res.track);
66
+ return new LocalAudioTrack(res.track!);
66
67
  }
67
68
  }
68
69
 
@@ -72,7 +73,7 @@ export class LocalVideoTrack extends Track {
72
73
  }
73
74
 
74
75
  static createVideoTrack(name: string, source: VideoSource): LocalVideoTrack {
75
- const req = new CreateVideoTrackRequest({
76
+ const req = create(CreateVideoTrackRequestSchema, {
76
77
  name: name,
77
78
  sourceHandle: source.ffiHandle.handle,
78
79
  });
@@ -81,7 +82,7 @@ export class LocalVideoTrack extends Track {
81
82
  message: { case: 'createVideoTrack', value: req },
82
83
  });
83
84
 
84
- return new LocalVideoTrack(res.track);
85
+ return new LocalVideoTrack(res.track!);
85
86
  }
86
87
  }
87
88
 
@@ -1,11 +1,12 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
+ import { create } from '@bufbuild/protobuf';
4
5
  import { FfiClient } from './ffi_client.js';
5
6
  import { FfiHandle } from './napi/native.js';
6
7
  import type { EncryptionType } from './proto/e2ee_pb.js';
7
8
  import type { SetSubscribedResponse } from './proto/room_pb.js';
8
- import { SetSubscribedRequest } from './proto/room_pb.js';
9
+ import { SetSubscribedRequestSchema } from './proto/room_pb.js';
9
10
  import type {
10
11
  OwnedTrackPublication,
11
12
  TrackKind,
@@ -23,8 +24,8 @@ export abstract class TrackPublication {
23
24
  track?: Track;
24
25
 
25
26
  constructor(ownedInfo: OwnedTrackPublication) {
26
- this.info = ownedInfo.info;
27
- this.ffiHandle = new FfiHandle(ownedInfo.handle.id);
27
+ this.info = ownedInfo.info!;
28
+ this.ffiHandle = new FfiHandle(ownedInfo!.handle!.id);
28
29
  }
29
30
 
30
31
  get sid(): string {
@@ -100,7 +101,7 @@ export class RemoteTrackPublication extends TrackPublication {
100
101
  }
101
102
 
102
103
  setSubscribed(subscribed: boolean) {
103
- const req = new SetSubscribedRequest({
104
+ const req = create(SetSubscribedRequestSchema, {
104
105
  subscribe: subscribed,
105
106
  publicationHandle: this.ffiHandle.handle,
106
107
  });
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const SDK_VERSION = "0.11.0";