@whereby.com/browser-sdk 2.0.0-alpha5 → 2.0.0-alpha7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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-alpha5", 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-alpha7", 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$1 = {
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$1);
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",
@@ -295,10 +531,10 @@ class ServerSocket {
295
531
  }
296
532
 
297
533
  this._socket = io__default["default"](hostName, options);
298
- this._socket.on("reconnect", () => {
534
+ this._socket.io.on("reconnect", () => {
299
535
  this._socket.sendBuffer = [];
300
536
  });
301
- this._socket.on("reconnect_attempt", () => {
537
+ this._socket.io.on("reconnect_attempt", () => {
302
538
  if (this._wasConnectedUsingWebsocket) {
303
539
  this._socket.io.opts.transports = ["websocket"];
304
540
  // only fallback to polling if not safari
@@ -356,6 +592,10 @@ class ServerSocket {
356
592
  );
357
593
  }
358
594
 
595
+ getManager() {
596
+ return this._socket.io;
597
+ }
598
+
359
599
  isConnecting() {
360
600
  return this._socket && this._socket.connecting;
361
601
  }
@@ -1989,10 +2229,11 @@ class VegaParser {
1989
2229
  }
1990
2230
 
1991
2231
  class VegaConnection extends EventEmitter.EventEmitter {
1992
- constructor(wsUrl, logger) {
2232
+ constructor(wsUrl, logger, protocol = "whereby-sfu#v4") {
1993
2233
  super();
1994
2234
 
1995
2235
  this.wsUrl = wsUrl;
2236
+ this.protocol = protocol;
1996
2237
  this.logger = logger;
1997
2238
 
1998
2239
  // This is the map of sent requests that are waiting for a response
@@ -2001,7 +2242,7 @@ class VegaConnection extends EventEmitter.EventEmitter {
2001
2242
  }
2002
2243
 
2003
2244
  _setupSocket() {
2004
- this.socket = new WebSocket(this.wsUrl, "whereby-sfu#v4");
2245
+ this.socket = new WebSocket(this.wsUrl, this.protocol);
2005
2246
  this.socket.onopen = this._onOpen.bind(this);
2006
2247
  this.socket.onmessage = this._onMessage.bind(this);
2007
2248
  this.socket.onclose = this._onClose.bind(this);
@@ -2021,7 +2262,9 @@ class VegaConnection extends EventEmitter.EventEmitter {
2021
2262
  }
2022
2263
 
2023
2264
  close() {
2024
- this.socket?.close();
2265
+ if (!this.socket) return;
2266
+
2267
+ this.socket.close();
2025
2268
  }
2026
2269
 
2027
2270
  _onOpen() {
@@ -2033,11 +2276,15 @@ class VegaConnection extends EventEmitter.EventEmitter {
2033
2276
  _onMessage(event) {
2034
2277
  const socketMessage = VegaParser.parse(event.data);
2035
2278
 
2279
+ if (!socketMessage) {
2280
+ return this.logger.log("VegaConnectionManager: Received invalid message", event.data);
2281
+ }
2282
+
2036
2283
  this.logger.log("VegaConnectionManager: Received message", socketMessage);
2037
2284
 
2038
- if (socketMessage?.response) {
2285
+ if (socketMessage.response) {
2039
2286
  this._handleResponse(socketMessage);
2040
- } else if (socketMessage?.message) {
2287
+ } else if (socketMessage.message) {
2041
2288
  this.emit("message", socketMessage);
2042
2289
  }
2043
2290
  }
@@ -2144,7 +2391,7 @@ const VIDEO_SETTINGS_VP9 = {
2144
2391
  codecOptions: {
2145
2392
  videoGoogleStartBitrate: 500,
2146
2393
  },
2147
- encodings: [{ scalabilityMode: "S3T3_KEY", networkPriority: "high" }],
2394
+ encodings: [{ scalabilityMode: "L3T2_KEY", networkPriority: "high" }],
2148
2395
  };
2149
2396
 
2150
2397
  const SCREEN_SHARE_SETTINGS = {
@@ -2153,13 +2400,13 @@ const SCREEN_SHARE_SETTINGS = {
2153
2400
 
2154
2401
  const SCREEN_SHARE_SIMULCAST_SETTINGS = {
2155
2402
  encodings: [
2156
- { dtx: true, maxBitrate: 500000 },
2157
- { dtx: true, maxBitrate: 1500000 },
2403
+ { scaleResolutionDownBy: 2, dtx: true, maxBitrate: 500000 },
2404
+ { scaleResolutionDownBy: 1, dtx: true, maxBitrate: 1500000 },
2158
2405
  ],
2159
2406
  };
2160
2407
 
2161
2408
  const SCREEN_SHARE_SETTINGS_VP9 = {
2162
- encodings: [{ scalabilityMode: "S3T3", dtx: true, networkPriority: "high" }],
2409
+ encodings: [{ scalabilityMode: "L1T1", dtx: true, networkPriority: "high" }],
2163
2410
  };
2164
2411
 
2165
2412
  const getMediaSettings = (kind, isScreenShare, features) => {
@@ -2175,8 +2422,8 @@ const getMediaSettings = (kind, isScreenShare, features) => {
2175
2422
 
2176
2423
  return SCREEN_SHARE_SETTINGS;
2177
2424
  } else {
2178
- if (lowDataModeEnabled) return VIDEO_SETTINGS_SD;
2179
2425
  if (vp9On) return VIDEO_SETTINGS_VP9;
2426
+ if (lowDataModeEnabled) return VIDEO_SETTINGS_SD;
2180
2427
 
2181
2428
  return VIDEO_SETTINGS_HD;
2182
2429
  }
@@ -5035,7 +5282,7 @@ const SIGNAL_BASE_URL = "wss://signal.appearin.net";
5035
5282
  const NON_PERSON_ROLES = ["recorder", "streamer"];
5036
5283
  function createSocket() {
5037
5284
  const parsedUrl = new URL(SIGNAL_BASE_URL);
5038
- const path = `${parsedUrl.pathname.replace(/^\/$/, "")}/protocol/socket.io/v1`;
5285
+ const path = `${parsedUrl.pathname.replace(/^\/$/, "")}/protocol/socket.io/v4`;
5039
5286
  const SOCKET_HOST = parsedUrl.origin;
5040
5287
  const socketConf = {
5041
5288
  host: SOCKET_HOST,
@@ -5051,11 +5298,12 @@ const noop = () => {
5051
5298
  };
5052
5299
  const TypedEventTarget = EventTarget;
5053
5300
  class RoomConnection extends TypedEventTarget {
5054
- constructor(roomUrl, { displayName, localMediaConstraints, localStream, logger }) {
5301
+ constructor(roomUrl, { displayName, localMediaConstraints, logger, localMedia }) {
5055
5302
  super();
5056
5303
  this.localParticipant = null;
5057
5304
  this.remoteParticipants = [];
5058
5305
  this.roomConnectionState = "";
5306
+ this._ownsLocalMedia = false;
5059
5307
  this.roomUrl = new URL(roomUrl); // Throw if invalid Whereby room url
5060
5308
  this.logger = logger || {
5061
5309
  debug: noop,
@@ -5064,10 +5312,19 @@ class RoomConnection extends TypedEventTarget {
5064
5312
  warn: noop,
5065
5313
  };
5066
5314
  this.displayName = displayName;
5067
- this.localStream = localStream;
5068
5315
  this.localMediaConstraints = localMediaConstraints;
5069
5316
  const urls = fromLocation({ host: this.roomUrl.host });
5070
- // Initialize services
5317
+ // Set up local media
5318
+ if (localMedia) {
5319
+ this.localMedia = localMedia;
5320
+ }
5321
+ else if (localMediaConstraints) {
5322
+ this.localMedia = new LocalMedia(localMediaConstraints);
5323
+ this._ownsLocalMedia = true;
5324
+ }
5325
+ else {
5326
+ throw new Error("Missing constraints");
5327
+ }
5071
5328
  this.credentialsService = CredentialsService.create({ baseUrl: API_BASE_URL });
5072
5329
  this.apiClient = new ApiClient({
5073
5330
  fetchDeviceCredentials: this.credentialsService.getCredentials.bind(this.credentialsService),
@@ -5093,6 +5350,15 @@ class RoomConnection extends TypedEventTarget {
5093
5350
  this.signalSocket.on("audio_enabled", this._handleClientAudioEnabled.bind(this));
5094
5351
  this.signalSocket.on("video_enabled", this._handleClientVideoEnabled.bind(this));
5095
5352
  this.signalSocket.on("client_metadata_received", this._handleClientMetadataReceived.bind(this));
5353
+ // Set up local media listeners
5354
+ this.localMedia.addEventListener("camera_enabled", (e) => {
5355
+ const { enabled } = e.detail;
5356
+ this.signalSocket.emit("enable_video", { enabled });
5357
+ });
5358
+ this.localMedia.addEventListener("microphone_enabled", (e) => {
5359
+ const { enabled } = e.detail;
5360
+ this.signalSocket.emit("enable_audio", { enabled });
5361
+ });
5096
5362
  }
5097
5363
  _handleNewClient({ client }) {
5098
5364
  if (NON_PERSON_ROLES.includes(client.role.roleName)) {
@@ -5150,10 +5416,11 @@ class RoomConnection extends TypedEventTarget {
5150
5416
  }
5151
5417
  }
5152
5418
  _handleRtcManagerCreated({ rtcManager }) {
5153
- var _a, _b, _c;
5419
+ var _a;
5154
5420
  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)));
5421
+ this.localMedia.addRtcManager(rtcManager);
5422
+ if (this.localMedia.stream) {
5423
+ (_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.addNewStream("0", this.localMedia.stream, !this.localMedia.isMicrophoneEnabled(), !this.localMedia.isCameraEnabled());
5157
5424
  }
5158
5425
  }
5159
5426
  _handleAcceptStreams(remoteParticipants) {
@@ -5178,8 +5445,6 @@ class RoomConnection extends TypedEventTarget {
5178
5445
  if (!newState) {
5179
5446
  return;
5180
5447
  }
5181
- // #endregion
5182
- // #region doAcceptStreams
5183
5448
  if (newState === "to_accept" ||
5184
5449
  (newState === "new_accept" && shouldAcceptNewClients) ||
5185
5450
  (newState === "old_accept" && !shouldAcceptNewClients)) {
@@ -5203,7 +5468,6 @@ class RoomConnection extends TypedEventTarget {
5203
5468
  else ;
5204
5469
  // Update stream state
5205
5470
  participant.updateStreamState(streamId, streamState.replace(/to_|new_|old_/, "done_"));
5206
- // #endregion
5207
5471
  });
5208
5472
  });
5209
5473
  }
@@ -5215,9 +5479,6 @@ class RoomConnection extends TypedEventTarget {
5215
5479
  }
5216
5480
  this.dispatchEvent(new CustomEvent("participant_stream_added", { detail: { participantId: clientId, stream, streamId } }));
5217
5481
  }
5218
- /**
5219
- * Public API
5220
- */
5221
5482
  join() {
5222
5483
  return tslib.__awaiter(this, void 0, void 0, function* () {
5223
5484
  if (["connected", "connecting"].includes(this.roomConnectionState)) {
@@ -5226,24 +5487,14 @@ class RoomConnection extends TypedEventTarget {
5226
5487
  }
5227
5488
  this.logger.log("Joining room");
5228
5489
  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");
5490
+ if (this._ownsLocalMedia) {
5491
+ yield this.localMedia.start();
5236
5492
  }
5237
- // TODO: Get room permissions
5238
- // TODO: Get room features
5239
5493
  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
- },
5494
+ getMediaConstraints: () => ({
5495
+ audio: this.localMedia.isMicrophoneEnabled(),
5496
+ video: this.localMedia.isCameraEnabled(),
5497
+ }),
5247
5498
  deferrable(clientId) {
5248
5499
  return !clientId;
5249
5500
  },
@@ -5264,20 +5515,24 @@ class RoomConnection extends TypedEventTarget {
5264
5515
  simulcastScreenshareOn: false,
5265
5516
  },
5266
5517
  });
5518
+ const organization = yield this.organizationServiceCache.fetchOrganization();
5519
+ if (!organization) {
5520
+ throw new Error("Invalid room url");
5521
+ }
5267
5522
  // Identify device on signal connection
5268
5523
  const deviceCredentials = yield this.credentialsService.getCredentials();
5524
+ this.signalSocket.connect();
5269
5525
  // TODO: Handle connection and failed connection properly
5270
- setTimeout(() => {
5526
+ this.signalSocket.on("connect", () => {
5271
5527
  this.logger.log("Connected to signal socket");
5272
5528
  this.signalSocket.emit("identify_device", { deviceCredentials });
5273
- }, 2000);
5529
+ });
5274
5530
  this.signalSocket.once("device_identified", () => {
5275
- var _a, _b;
5276
5531
  this.signalSocket.emit("join_room", {
5277
5532
  avatarUrl: null,
5278
5533
  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)),
5534
+ isAudioEnabled: this.localMedia.isMicrophoneEnabled(),
5535
+ isVideoEnabled: this.localMedia.isCameraEnabled(),
5281
5536
  },
5282
5537
  deviceCapabilities: { canScreenshare: true },
5283
5538
  displayName: this.displayName,
@@ -5288,7 +5543,7 @@ class RoomConnection extends TypedEventTarget {
5288
5543
  roomKey: null,
5289
5544
  roomName: this.roomUrl.pathname,
5290
5545
  selfId: "",
5291
- userAgent: `browser-sdk:${sdkVersion }`
5546
+ userAgent: `browser-sdk:${sdkVersion }`,
5292
5547
  });
5293
5548
  });
5294
5549
  this.signalSocket.once("room_joined", (res) => {
@@ -5296,7 +5551,7 @@ class RoomConnection extends TypedEventTarget {
5296
5551
  const localClient = clients.find((c) => c.id === selfId);
5297
5552
  if (!localClient)
5298
5553
  throw new Error("Missing local client");
5299
- this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localStream }));
5554
+ this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localMedia.stream || undefined }));
5300
5555
  this.remoteParticipants = clients
5301
5556
  .filter((c) => c.id !== selfId)
5302
5557
  .map((c) => new RemoteParticipant(Object.assign(Object.assign({}, c), { newJoiner: false })));
@@ -5316,6 +5571,14 @@ class RoomConnection extends TypedEventTarget {
5316
5571
  }
5317
5572
  leave() {
5318
5573
  return new Promise((resolve) => {
5574
+ if (this._ownsLocalMedia) {
5575
+ this.localMedia.stop();
5576
+ }
5577
+ if (this.rtcManager) {
5578
+ this.localMedia.removeRtcManager(this.rtcManager);
5579
+ this.rtcManager.disconnectAll();
5580
+ this.rtcManager = undefined;
5581
+ }
5319
5582
  if (!this.signalSocket) {
5320
5583
  return resolve();
5321
5584
  }
@@ -5330,30 +5593,6 @@ class RoomConnection extends TypedEventTarget {
5330
5593
  });
5331
5594
  });
5332
5595
  }
5333
- toggleCamera(enabled) {
5334
- var _a;
5335
- const localVideoTrack = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks()[0];
5336
- if (!localVideoTrack) {
5337
- this.logger.log("Tried toggling non-existing video track");
5338
- return;
5339
- }
5340
- // TODO: Do stopOrResumeVideo
5341
- const newValue = enabled !== null && enabled !== void 0 ? enabled : !localVideoTrack.enabled;
5342
- localVideoTrack.enabled = newValue;
5343
- this.signalSocket.emit("enable_video", { enabled: newValue });
5344
- }
5345
- toggleMicrophone(enabled) {
5346
- var _a;
5347
- const localAudioTrack = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
5348
- if (!localAudioTrack) {
5349
- this.logger.log("Tried toggling non-existing audio track");
5350
- return;
5351
- }
5352
- // TODO: Do stopOrResumeAudio
5353
- const newValue = enabled !== null && enabled !== void 0 ? enabled : !localAudioTrack.enabled;
5354
- localAudioTrack.enabled = newValue;
5355
- this.signalSocket.emit("enable_audio", { enabled: newValue });
5356
- }
5357
5596
  setDisplayName(displayName) {
5358
5597
  this.signalSocket.emit("send_client_metadata", {
5359
5598
  type: "UserData",
@@ -5364,6 +5603,11 @@ class RoomConnection extends TypedEventTarget {
5364
5603
  }
5365
5604
  }
5366
5605
 
5606
+ const initialState = {
5607
+ isJoining: false,
5608
+ joinError: null,
5609
+ remoteParticipants: [],
5610
+ };
5367
5611
  function updateParticipant(remoteParticipants, participantId, updates) {
5368
5612
  const existingParticipant = remoteParticipants.find((p) => p.id === participantId);
5369
5613
  if (!existingParticipant) {
@@ -5407,18 +5651,14 @@ function reducer(state, action) {
5407
5651
  default:
5408
5652
  throw state;
5409
5653
  }
5410
- }
5411
-
5654
+ }
5412
5655
  function useRoomConnection(roomUrl, roomConnectionOptions) {
5413
- const [roomConnection, setRoomConnection] = React.useState(null);
5414
- const [state, dispatch] = React.useReducer(reducer, { remoteParticipants: [] });
5415
- React.useEffect(() => {
5416
- setRoomConnection(new RoomConnection(roomUrl, roomConnectionOptions));
5417
- }, [roomUrl]);
5656
+ const [roomConnection] = React.useState(() => {
5657
+ var _a;
5658
+ return 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 }));
5659
+ });
5660
+ const [state, dispatch] = React.useReducer(reducer, initialState);
5418
5661
  React.useEffect(() => {
5419
- if (!roomConnection) {
5420
- return;
5421
- }
5422
5662
  roomConnection.addEventListener("participant_audio_enabled", (e) => {
5423
5663
  const { participantId, isAudioEnabled } = e.detail;
5424
5664
  dispatch({ type: "PARTICIPANT_AUDIO_ENABLED", payload: { participantId, isAudioEnabled } });
@@ -5451,28 +5691,31 @@ function useRoomConnection(roomUrl, roomConnectionOptions) {
5451
5691
  return () => {
5452
5692
  roomConnection.leave();
5453
5693
  };
5454
- }, [roomConnection]);
5455
- return [
5694
+ }, []);
5695
+ return {
5456
5696
  state,
5457
- {
5697
+ actions: {
5458
5698
  toggleCamera: (enabled) => {
5459
- roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.toggleCamera(enabled);
5699
+ roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.localMedia.toggleCameraEnabled(enabled);
5460
5700
  },
5461
5701
  toggleMicrophone: (enabled) => {
5462
- roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.toggleMicrophone(enabled);
5702
+ roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.localMedia.toggleMichrophoneEnabled(enabled);
5463
5703
  },
5464
5704
  setDisplayName: (displayName) => {
5465
5705
  roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.setDisplayName(displayName);
5466
5706
  dispatch({ type: "LOCAL_CLIENT_DISPLAY_NAME_CHANGED", payload: { displayName } });
5467
5707
  },
5468
5708
  },
5469
- {
5709
+ components: {
5470
5710
  VideoView,
5471
5711
  },
5472
- ];
5712
+ _ref: roomConnection,
5713
+ };
5473
5714
  }
5474
5715
 
5475
- const sdkVersion = "2.0.0-alpha5";
5716
+ const sdkVersion = "2.0.0-alpha7";
5476
5717
 
5718
+ exports.VideoView = VideoView;
5477
5719
  exports.sdkVersion = sdkVersion;
5720
+ exports.useLocalMedia = useLocalMedia;
5478
5721
  exports.useRoomConnection = useRoomConnection;