@whereby.com/browser-sdk 2.0.0-alpha4 → 2.0.0-alpha6

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 CHANGED
@@ -20,6 +20,42 @@ yarn add @whereby.com/browser-sdk
20
20
 
21
21
  ### React hooks
22
22
 
23
+ #### useLocalMedia
24
+ The `useLocalMedia` hook enables preview and selection of local devices (camera & microphone) prior to establishing a connection within a Whereby room. Use this hook to build rich pre-call
25
+ experiences, allowing end users to confirm their device selection up-front. This hook works seamlessly with the `useRoomConnection` hook described below.
26
+
27
+ ```
28
+ import { useLocalMedia, VideoView } from “@whereby.com/browser-sdk”;
29
+
30
+ function MyPreCallUX() {
31
+ const localMedia = useLocalMedia({ audio: false, video: true });
32
+
33
+ const { currentCameraDeviceId, cameraDevices, localStream } = localMedia.state;
34
+ const { setCameraDevice, toggleCameraEnabled } = localMedia.actions;
35
+ const { VideoView } = components;
36
+
37
+ return <div className="preCallView">
38
+ { /* Render any UI, making use of state */ }
39
+ { cameraDevices.map((d) => (
40
+ <p
41
+ key={d.deviceId}
42
+ onClick={() => {
43
+ if (d.deviceId !== currentCameraDeviceId) {
44
+ setCameraDevice(d.deviceId);
45
+ }
46
+ }}
47
+ >
48
+ {d.label}
49
+ </p>
50
+ )) }
51
+ <VideoView muted stream={localStream} />
52
+ </div>;
53
+ }
54
+
55
+ ```
56
+
57
+
58
+ #### useRoomConnection
23
59
  The `useRoomConnection` hook provides a way to connect participants in a given room, subscribe to state updates, and perform actions on the connection, like toggling camera or microphone.
24
60
 
25
61
  ```
@@ -29,10 +65,11 @@ function MyCallUX( { roomUrl, localStream }) {
29
65
  const [state, actions, components ] = useRoomConnection(
30
66
  "<room_url>"
31
67
  {
32
- localMediaConstraints: {
68
+ localMedia: null, // Supply localMedia from `useLocalMedia` hook, or constraints below
69
+ localMediaConstraints: {
33
70
  audio: true,
34
71
  video: true,
35
- },
72
+ }
36
73
  }
37
74
  );
38
75
 
package/dist/lib.cjs.js CHANGED
@@ -130,7 +130,7 @@ heresy.define("WherebyEmbed", {
130
130
  if (!subdomain)
131
131
  return this.html `Whereby: Missing subdomain attr.`;
132
132
  const url = new URL(room, `https://${subdomain}.whereby.com`);
133
- Object.entries(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ jsApi: true, we: "2.0.0-alpha4", iframeSource: subdomain }, (displayName && { displayName })), (lang && { lang })), (metadata && { metadata })), (groups && { groups })), (virtualBackgroundUrl && { virtualBackgroundUrl })), (avatarUrl && { avatarUrl })), (minimal != null && { embed: minimal })), boolAttrs.reduce(
133
+ Object.entries(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ jsApi: true, we: "2.0.0-alpha6", iframeSource: subdomain }, (displayName && { displayName })), (lang && { lang })), (metadata && { metadata })), (groups && { groups })), (virtualBackgroundUrl && { virtualBackgroundUrl })), (avatarUrl && { avatarUrl })), (minimal != null && { embed: minimal })), boolAttrs.reduce(
134
134
  // add to URL if set in any way
135
135
  (o, v) => (this[v.toLowerCase()] != null ? Object.assign(Object.assign({}, o), { [v]: this[v.toLowerCase()] }) : o), {}))).forEach(([k, v]) => {
136
136
  if (!url.searchParams.has(k) && typeof v === "string") {
@@ -165,6 +165,242 @@ var VideoView = (_a) => {
165
165
  return React__default["default"].createElement("video", Object.assign({ ref: videoEl, autoPlay: true, playsInline: true }, rest));
166
166
  };
167
167
 
168
+ const TypedLocalMediaEventTarget = EventTarget;
169
+ class LocalMedia extends TypedLocalMediaEventTarget {
170
+ constructor(constraints) {
171
+ super();
172
+ this._constraints = constraints;
173
+ this.stream = new MediaStream();
174
+ this._rtcManagers = [];
175
+ navigator.mediaDevices.addEventListener("devicechange", this._updateDeviceList.bind(this));
176
+ }
177
+ addRtcManager(rtcManager) {
178
+ this._rtcManagers.push(rtcManager);
179
+ }
180
+ removeRtcManager(rtcManager) {
181
+ this._rtcManagers = this._rtcManagers.filter((r) => r !== rtcManager);
182
+ }
183
+ getCameraDeviceId() {
184
+ var _a;
185
+ return (_a = this.stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
186
+ }
187
+ getMicrophoneDeviceId() {
188
+ var _a;
189
+ return (_a = this.stream.getAudioTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
190
+ }
191
+ isCameraEnabled() {
192
+ var _a;
193
+ return !!((_a = this.stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.enabled);
194
+ }
195
+ isMicrophoneEnabled() {
196
+ var _a;
197
+ return !!((_a = this.stream.getAudioTracks()[0]) === null || _a === void 0 ? void 0 : _a.enabled);
198
+ }
199
+ toggleCameraEnabled(enabled) {
200
+ const videoTrack = this.stream.getVideoTracks()[0];
201
+ if (!videoTrack) {
202
+ return;
203
+ }
204
+ const newValue = enabled !== null && enabled !== void 0 ? enabled : !videoTrack.enabled;
205
+ videoTrack.enabled = newValue;
206
+ this.dispatchEvent(new CustomEvent("camera_enabled", { detail: { enabled: newValue } }));
207
+ }
208
+ toggleMichrophoneEnabled(enabled) {
209
+ const audioTrack = this.stream.getAudioTracks()[0];
210
+ if (!audioTrack) {
211
+ return;
212
+ }
213
+ const newValue = enabled !== null && enabled !== void 0 ? enabled : !audioTrack.enabled;
214
+ audioTrack.enabled = newValue;
215
+ this.dispatchEvent(new CustomEvent("microphone_enabled", { detail: { enabled: newValue } }));
216
+ }
217
+ setCameraDevice(deviceId) {
218
+ return tslib.__awaiter(this, void 0, void 0, function* () {
219
+ const newStream = yield navigator.mediaDevices.getUserMedia({ video: { deviceId } });
220
+ const newVideoTrack = newStream.getVideoTracks()[0];
221
+ if (newVideoTrack) {
222
+ const oldVideoTrack = this.stream.getVideoTracks()[0];
223
+ newVideoTrack.enabled = oldVideoTrack.enabled;
224
+ oldVideoTrack === null || oldVideoTrack === void 0 ? void 0 : oldVideoTrack.stop();
225
+ this._rtcManagers.forEach((rtcManager) => {
226
+ rtcManager.replaceTrack(oldVideoTrack, newVideoTrack);
227
+ });
228
+ this.stream.removeTrack(oldVideoTrack);
229
+ this.stream.addTrack(newVideoTrack);
230
+ }
231
+ this.dispatchEvent(new CustomEvent("stream_updated", {
232
+ detail: { stream: this.stream },
233
+ }));
234
+ });
235
+ }
236
+ setMicrophoneDevice(deviceId) {
237
+ return tslib.__awaiter(this, void 0, void 0, function* () {
238
+ const newStream = yield navigator.mediaDevices.getUserMedia({ audio: { deviceId } });
239
+ const newAudioTrack = newStream.getAudioTracks()[0];
240
+ const oldAudioTrack = this.stream.getAudioTracks()[0];
241
+ if (oldAudioTrack) {
242
+ newAudioTrack.enabled = oldAudioTrack.enabled;
243
+ oldAudioTrack.stop();
244
+ this.stream.removeTrack(oldAudioTrack);
245
+ }
246
+ this._rtcManagers.forEach((rtcManager) => {
247
+ rtcManager.replaceTrack(oldAudioTrack, newAudioTrack);
248
+ });
249
+ this.stream.addTrack(newAudioTrack);
250
+ this.dispatchEvent(new CustomEvent("stream_updated", {
251
+ detail: { stream: this.stream },
252
+ }));
253
+ });
254
+ }
255
+ _updateDeviceList() {
256
+ return tslib.__awaiter(this, void 0, void 0, function* () {
257
+ try {
258
+ const devices = yield navigator.mediaDevices.enumerateDevices();
259
+ this.dispatchEvent(new CustomEvent("device_list_updated", {
260
+ detail: {
261
+ cameraDevices: devices.filter((d) => d.kind === "videoinput"),
262
+ microphoneDevices: devices.filter((d) => d.kind === "audioinput"),
263
+ speakerDevices: devices.filter((d) => d.kind === "audiooutput"),
264
+ },
265
+ }));
266
+ }
267
+ catch (error) {
268
+ this.dispatchEvent(new CustomEvent("device_list_update_error", {
269
+ detail: {
270
+ error,
271
+ },
272
+ }));
273
+ throw error;
274
+ }
275
+ });
276
+ }
277
+ start() {
278
+ return tslib.__awaiter(this, void 0, void 0, function* () {
279
+ const newStream = yield navigator.mediaDevices.getUserMedia(this._constraints);
280
+ newStream.getTracks().forEach((t) => this.stream.addTrack(t));
281
+ this._updateDeviceList();
282
+ this.dispatchEvent(new CustomEvent("stream_updated", {
283
+ detail: { stream: this.stream },
284
+ }));
285
+ return this.stream;
286
+ });
287
+ }
288
+ stop() {
289
+ var _a;
290
+ (_a = this.stream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => {
291
+ t.stop();
292
+ });
293
+ }
294
+ }
295
+
296
+ const initialState = {
297
+ cameraDeviceError: null,
298
+ cameraDevices: [],
299
+ isSettingCameraDevice: false,
300
+ isSettingMicrophoneDevice: false,
301
+ isStarting: false,
302
+ microphoneDeviceError: null,
303
+ microphoneDevices: [],
304
+ speakerDevices: [],
305
+ startError: null,
306
+ };
307
+ function reducer$1(state, action) {
308
+ switch (action.type) {
309
+ case "DEVICE_LIST_UPDATED":
310
+ return Object.assign(Object.assign({}, state), action.payload);
311
+ case "LOCAL_STREAM_UPDATED":
312
+ return Object.assign(Object.assign({}, state), { currentCameraDeviceId: action.payload.currentCameraDeviceId, currentMicrophoneDeviceId: action.payload.currentMicrophoneDeviceId, localStream: action.payload.stream });
313
+ case "SET_CAMERA_DEVICE":
314
+ return Object.assign(Object.assign({}, state), { cameraDeviceError: null, isSettingCameraDevice: true });
315
+ case "SET_CAMERA_DEVICE_COMPLETE":
316
+ return Object.assign(Object.assign({}, state), { isSettingCameraDevice: false });
317
+ case "SET_CAMERA_DEVICE_ERROR":
318
+ return Object.assign(Object.assign({}, state), { cameraDeviceError: action.payload, isSettingCameraDevice: false });
319
+ case "SET_MICROPHONE_DEVICE":
320
+ return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: true, microphoneDeviceError: null });
321
+ case "SET_MICROPHONE_DEVICE_COMPLETE":
322
+ return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: false });
323
+ case "SET_MICROPHONE_DEVICE_ERROR":
324
+ return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: false, microphoneDeviceError: action.payload });
325
+ case "START":
326
+ return Object.assign(Object.assign({}, state), { isStarting: true, startError: null });
327
+ case "START_COMPLETE":
328
+ return Object.assign(Object.assign({}, state), { isStarting: false });
329
+ case "START_ERROR":
330
+ return Object.assign(Object.assign({}, state), { isStarting: false, startError: action.payload });
331
+ default:
332
+ return state;
333
+ }
334
+ }
335
+ function useLocalMedia(constraints = { audio: true, video: true }) {
336
+ const [localMedia] = React.useState(() => new LocalMedia(constraints));
337
+ const [state, dispatch] = React.useReducer(reducer$1, initialState);
338
+ React.useEffect(() => {
339
+ localMedia.addEventListener("device_list_updated", (e) => {
340
+ const { cameraDevices, microphoneDevices, speakerDevices } = e.detail;
341
+ dispatch({ type: "DEVICE_LIST_UPDATED", payload: { cameraDevices, microphoneDevices, speakerDevices } });
342
+ });
343
+ localMedia.addEventListener("stream_updated", (e) => {
344
+ const { stream } = e.detail;
345
+ dispatch({
346
+ type: "LOCAL_STREAM_UPDATED",
347
+ payload: {
348
+ stream,
349
+ currentCameraDeviceId: localMedia.getCameraDeviceId(),
350
+ currentMicrophoneDeviceId: localMedia.getMicrophoneDeviceId(),
351
+ },
352
+ });
353
+ });
354
+ const start = () => tslib.__awaiter(this, void 0, void 0, function* () {
355
+ dispatch({ type: "START" });
356
+ try {
357
+ yield localMedia.start();
358
+ dispatch({ type: "START_COMPLETE" });
359
+ }
360
+ catch (error) {
361
+ dispatch({ type: "START_ERROR", payload: error });
362
+ }
363
+ });
364
+ start();
365
+ // Perform cleanup on unmount
366
+ return () => {
367
+ localMedia.stop();
368
+ };
369
+ }, []);
370
+ return {
371
+ state,
372
+ actions: {
373
+ setCameraDevice: (...args) => tslib.__awaiter(this, void 0, void 0, function* () {
374
+ dispatch({ type: "SET_CAMERA_DEVICE" });
375
+ try {
376
+ yield localMedia.setCameraDevice(...args);
377
+ dispatch({ type: "SET_CAMERA_DEVICE_COMPLETE" });
378
+ }
379
+ catch (error) {
380
+ dispatch({ type: "SET_CAMERA_DEVICE_ERROR", payload: error });
381
+ }
382
+ }),
383
+ setMicrophoneDevice: (...args) => tslib.__awaiter(this, void 0, void 0, function* () {
384
+ dispatch({ type: "SET_MICROPHONE_DEVICE" });
385
+ try {
386
+ yield localMedia.setMicrophoneDevice(...args);
387
+ dispatch({ type: "SET_MICROPHONE_DEVICE_COMPLETE" });
388
+ }
389
+ catch (error) {
390
+ dispatch({ type: "SET_MICROPHONE_DEVICE_ERROR", payload: error });
391
+ }
392
+ }),
393
+ toggleCameraEnabled: (...args) => {
394
+ return localMedia.toggleCameraEnabled(...args);
395
+ },
396
+ toggleMicrophoneEnabled: (...args) => {
397
+ return localMedia.toggleMichrophoneEnabled(...args);
398
+ },
399
+ },
400
+ _ref: localMedia,
401
+ };
402
+ }
403
+
168
404
  const EVENTS = {
169
405
  CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
170
406
  STREAM_ADDED: "stream_added",
@@ -5051,11 +5287,12 @@ const noop = () => {
5051
5287
  };
5052
5288
  const TypedEventTarget = EventTarget;
5053
5289
  class RoomConnection extends TypedEventTarget {
5054
- constructor(roomUrl, { displayName, localMediaConstraints, localStream, logger }) {
5290
+ constructor(roomUrl, { displayName, localMediaConstraints, logger, localMedia }) {
5055
5291
  super();
5056
5292
  this.localParticipant = null;
5057
5293
  this.remoteParticipants = [];
5058
5294
  this.roomConnectionState = "";
5295
+ this._ownsLocalMedia = false;
5059
5296
  this.roomUrl = new URL(roomUrl); // Throw if invalid Whereby room url
5060
5297
  this.logger = logger || {
5061
5298
  debug: noop,
@@ -5064,10 +5301,19 @@ class RoomConnection extends TypedEventTarget {
5064
5301
  warn: noop,
5065
5302
  };
5066
5303
  this.displayName = displayName;
5067
- this.localStream = localStream;
5068
5304
  this.localMediaConstraints = localMediaConstraints;
5069
5305
  const urls = fromLocation({ host: this.roomUrl.host });
5070
- // Initialize services
5306
+ // Set up local media
5307
+ if (localMedia) {
5308
+ this.localMedia = localMedia;
5309
+ }
5310
+ else if (localMediaConstraints) {
5311
+ this.localMedia = new LocalMedia(localMediaConstraints);
5312
+ this._ownsLocalMedia = true;
5313
+ }
5314
+ else {
5315
+ throw new Error("Missing constraints");
5316
+ }
5071
5317
  this.credentialsService = CredentialsService.create({ baseUrl: API_BASE_URL });
5072
5318
  this.apiClient = new ApiClient({
5073
5319
  fetchDeviceCredentials: this.credentialsService.getCredentials.bind(this.credentialsService),
@@ -5093,6 +5339,15 @@ class RoomConnection extends TypedEventTarget {
5093
5339
  this.signalSocket.on("audio_enabled", this._handleClientAudioEnabled.bind(this));
5094
5340
  this.signalSocket.on("video_enabled", this._handleClientVideoEnabled.bind(this));
5095
5341
  this.signalSocket.on("client_metadata_received", this._handleClientMetadataReceived.bind(this));
5342
+ // Set up local media listeners
5343
+ this.localMedia.addEventListener("camera_enabled", (e) => {
5344
+ const { enabled } = e.detail;
5345
+ this.signalSocket.emit("enable_video", { enabled });
5346
+ });
5347
+ this.localMedia.addEventListener("microphone_enabled", (e) => {
5348
+ const { enabled } = e.detail;
5349
+ this.signalSocket.emit("enable_audio", { enabled });
5350
+ });
5096
5351
  }
5097
5352
  _handleNewClient({ client }) {
5098
5353
  if (NON_PERSON_ROLES.includes(client.role.roleName)) {
@@ -5150,10 +5405,11 @@ class RoomConnection extends TypedEventTarget {
5150
5405
  }
5151
5406
  }
5152
5407
  _handleRtcManagerCreated({ rtcManager }) {
5153
- var _a, _b, _c;
5408
+ var _a;
5154
5409
  this.rtcManager = rtcManager;
5155
- if (this.localStream) {
5156
- (_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.addNewStream("0", this.localStream, !((_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getAudioTracks().find((t) => t.enabled)), !((_c = this.localStream) === null || _c === void 0 ? void 0 : _c.getVideoTracks().find((t) => t.enabled)));
5410
+ this.localMedia.addRtcManager(rtcManager);
5411
+ if (this.localMedia.stream) {
5412
+ (_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.addNewStream("0", this.localMedia.stream, !this.localMedia.isMicrophoneEnabled(), !this.localMedia.isCameraEnabled());
5157
5413
  }
5158
5414
  }
5159
5415
  _handleAcceptStreams(remoteParticipants) {
@@ -5178,8 +5434,6 @@ class RoomConnection extends TypedEventTarget {
5178
5434
  if (!newState) {
5179
5435
  return;
5180
5436
  }
5181
- // #endregion
5182
- // #region doAcceptStreams
5183
5437
  if (newState === "to_accept" ||
5184
5438
  (newState === "new_accept" && shouldAcceptNewClients) ||
5185
5439
  (newState === "old_accept" && !shouldAcceptNewClients)) {
@@ -5203,7 +5457,6 @@ class RoomConnection extends TypedEventTarget {
5203
5457
  else ;
5204
5458
  // Update stream state
5205
5459
  participant.updateStreamState(streamId, streamState.replace(/to_|new_|old_/, "done_"));
5206
- // #endregion
5207
5460
  });
5208
5461
  });
5209
5462
  }
@@ -5215,9 +5468,6 @@ class RoomConnection extends TypedEventTarget {
5215
5468
  }
5216
5469
  this.dispatchEvent(new CustomEvent("participant_stream_added", { detail: { participantId: clientId, stream, streamId } }));
5217
5470
  }
5218
- /**
5219
- * Public API
5220
- */
5221
5471
  join() {
5222
5472
  return tslib.__awaiter(this, void 0, void 0, function* () {
5223
5473
  if (["connected", "connecting"].includes(this.roomConnectionState)) {
@@ -5226,24 +5476,16 @@ class RoomConnection extends TypedEventTarget {
5226
5476
  }
5227
5477
  this.logger.log("Joining room");
5228
5478
  this.roomConnectionState = "connecting";
5229
- if (!this.localStream && this.localMediaConstraints) {
5230
- const localStream = yield navigator.mediaDevices.getUserMedia(this.localMediaConstraints);
5231
- this.localStream = localStream;
5232
- }
5233
- const organization = yield this.organizationServiceCache.fetchOrganization();
5234
- if (!organization) {
5235
- throw new Error("Invalid room url");
5479
+ if (this._ownsLocalMedia) {
5480
+ yield this.localMedia.start();
5236
5481
  }
5237
5482
  // TODO: Get room permissions
5238
5483
  // TODO: Get room features
5239
5484
  const webrtcProvider = {
5240
- getMediaConstraints: () => {
5241
- var _a, _b;
5242
- return ({
5243
- audio: !!((_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().find((t) => t.enabled)),
5244
- video: !!((_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getVideoTracks().find((t) => t.enabled)),
5245
- });
5246
- },
5485
+ getMediaConstraints: () => ({
5486
+ audio: this.localMedia.isMicrophoneEnabled(),
5487
+ video: this.localMedia.isCameraEnabled(),
5488
+ }),
5247
5489
  deferrable(clientId) {
5248
5490
  return !clientId;
5249
5491
  },
@@ -5264,6 +5506,10 @@ class RoomConnection extends TypedEventTarget {
5264
5506
  simulcastScreenshareOn: false,
5265
5507
  },
5266
5508
  });
5509
+ const organization = yield this.organizationServiceCache.fetchOrganization();
5510
+ if (!organization) {
5511
+ throw new Error("Invalid room url");
5512
+ }
5267
5513
  // Identify device on signal connection
5268
5514
  const deviceCredentials = yield this.credentialsService.getCredentials();
5269
5515
  // TODO: Handle connection and failed connection properly
@@ -5272,12 +5518,11 @@ class RoomConnection extends TypedEventTarget {
5272
5518
  this.signalSocket.emit("identify_device", { deviceCredentials });
5273
5519
  }, 2000);
5274
5520
  this.signalSocket.once("device_identified", () => {
5275
- var _a, _b;
5276
5521
  this.signalSocket.emit("join_room", {
5277
5522
  avatarUrl: null,
5278
5523
  config: {
5279
- isAudioEnabled: !!((_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().find((t) => t.enabled)),
5280
- isVideoEnabled: !!((_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getVideoTracks().find((t) => t.enabled)),
5524
+ isAudioEnabled: this.localMedia.isMicrophoneEnabled(),
5525
+ isVideoEnabled: this.localMedia.isCameraEnabled(),
5281
5526
  },
5282
5527
  deviceCapabilities: { canScreenshare: true },
5283
5528
  displayName: this.displayName,
@@ -5288,6 +5533,7 @@ class RoomConnection extends TypedEventTarget {
5288
5533
  roomKey: null,
5289
5534
  roomName: this.roomUrl.pathname,
5290
5535
  selfId: "",
5536
+ userAgent: `browser-sdk:${sdkVersion }`,
5291
5537
  });
5292
5538
  });
5293
5539
  this.signalSocket.once("room_joined", (res) => {
@@ -5295,7 +5541,7 @@ class RoomConnection extends TypedEventTarget {
5295
5541
  const localClient = clients.find((c) => c.id === selfId);
5296
5542
  if (!localClient)
5297
5543
  throw new Error("Missing local client");
5298
- this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localStream }));
5544
+ this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localMedia.stream || undefined }));
5299
5545
  this.remoteParticipants = clients
5300
5546
  .filter((c) => c.id !== selfId)
5301
5547
  .map((c) => new RemoteParticipant(Object.assign(Object.assign({}, c), { newJoiner: false })));
@@ -5315,6 +5561,14 @@ class RoomConnection extends TypedEventTarget {
5315
5561
  }
5316
5562
  leave() {
5317
5563
  return new Promise((resolve) => {
5564
+ if (this._ownsLocalMedia) {
5565
+ this.localMedia.stop();
5566
+ }
5567
+ if (this.rtcManager) {
5568
+ this.localMedia.removeRtcManager(this.rtcManager);
5569
+ this.rtcManager.disconnectAll();
5570
+ this.rtcManager = undefined;
5571
+ }
5318
5572
  if (!this.signalSocket) {
5319
5573
  return resolve();
5320
5574
  }
@@ -5329,30 +5583,6 @@ class RoomConnection extends TypedEventTarget {
5329
5583
  });
5330
5584
  });
5331
5585
  }
5332
- toggleCamera(enabled) {
5333
- var _a;
5334
- const localVideoTrack = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks()[0];
5335
- if (!localVideoTrack) {
5336
- this.logger.log("Tried toggling non-existing video track");
5337
- return;
5338
- }
5339
- // TODO: Do stopOrResumeVideo
5340
- const newValue = enabled !== null && enabled !== void 0 ? enabled : !localVideoTrack.enabled;
5341
- localVideoTrack.enabled = newValue;
5342
- this.signalSocket.emit("enable_video", { enabled: newValue });
5343
- }
5344
- toggleMicrophone(enabled) {
5345
- var _a;
5346
- const localAudioTrack = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
5347
- if (!localAudioTrack) {
5348
- this.logger.log("Tried toggling non-existing audio track");
5349
- return;
5350
- }
5351
- // TODO: Do stopOrResumeAudio
5352
- const newValue = enabled !== null && enabled !== void 0 ? enabled : !localAudioTrack.enabled;
5353
- localAudioTrack.enabled = newValue;
5354
- this.signalSocket.emit("enable_audio", { enabled: newValue });
5355
- }
5356
5586
  setDisplayName(displayName) {
5357
5587
  this.signalSocket.emit("send_client_metadata", {
5358
5588
  type: "UserData",
@@ -5412,7 +5642,8 @@ function useRoomConnection(roomUrl, roomConnectionOptions) {
5412
5642
  const [roomConnection, setRoomConnection] = React.useState(null);
5413
5643
  const [state, dispatch] = React.useReducer(reducer, { remoteParticipants: [] });
5414
5644
  React.useEffect(() => {
5415
- setRoomConnection(new RoomConnection(roomUrl, roomConnectionOptions));
5645
+ var _a;
5646
+ setRoomConnection(new RoomConnection(roomUrl, Object.assign(Object.assign({}, roomConnectionOptions), { localMedia: ((_a = roomConnectionOptions === null || roomConnectionOptions === void 0 ? void 0 : roomConnectionOptions.localMedia) === null || _a === void 0 ? void 0 : _a._ref) || undefined })));
5416
5647
  }, [roomUrl]);
5417
5648
  React.useEffect(() => {
5418
5649
  if (!roomConnection) {
@@ -5455,10 +5686,10 @@ function useRoomConnection(roomUrl, roomConnectionOptions) {
5455
5686
  state,
5456
5687
  {
5457
5688
  toggleCamera: (enabled) => {
5458
- roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.toggleCamera(enabled);
5689
+ roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.localMedia.toggleCameraEnabled(enabled);
5459
5690
  },
5460
5691
  toggleMicrophone: (enabled) => {
5461
- roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.toggleMicrophone(enabled);
5692
+ roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.localMedia.toggleMichrophoneEnabled(enabled);
5462
5693
  },
5463
5694
  setDisplayName: (displayName) => {
5464
5695
  roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.setDisplayName(displayName);
@@ -5471,7 +5702,9 @@ function useRoomConnection(roomUrl, roomConnectionOptions) {
5471
5702
  ];
5472
5703
  }
5473
5704
 
5474
- const sdkVersion = "2.0.0-alpha4";
5705
+ const sdkVersion = "2.0.0-alpha6";
5475
5706
 
5707
+ exports.VideoView = VideoView;
5476
5708
  exports.sdkVersion = sdkVersion;
5709
+ exports.useLocalMedia = useLocalMedia;
5477
5710
  exports.useRoomConnection = useRoomConnection;