@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.esm.js CHANGED
@@ -115,7 +115,7 @@ define("WherebyEmbed", {
115
115
  if (!subdomain)
116
116
  return this.html `Whereby: Missing subdomain attr.`;
117
117
  const url = new URL(room, `https://${subdomain}.whereby.com`);
118
- 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(
118
+ 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(
119
119
  // add to URL if set in any way
120
120
  (o, v) => (this[v.toLowerCase()] != null ? Object.assign(Object.assign({}, o), { [v]: this[v.toLowerCase()] }) : o), {}))).forEach(([k, v]) => {
121
121
  if (!url.searchParams.has(k) && typeof v === "string") {
@@ -150,6 +150,242 @@ var VideoView = (_a) => {
150
150
  return React.createElement("video", Object.assign({ ref: videoEl, autoPlay: true, playsInline: true }, rest));
151
151
  };
152
152
 
153
+ const TypedLocalMediaEventTarget = EventTarget;
154
+ class LocalMedia extends TypedLocalMediaEventTarget {
155
+ constructor(constraints) {
156
+ super();
157
+ this._constraints = constraints;
158
+ this.stream = new MediaStream();
159
+ this._rtcManagers = [];
160
+ navigator.mediaDevices.addEventListener("devicechange", this._updateDeviceList.bind(this));
161
+ }
162
+ addRtcManager(rtcManager) {
163
+ this._rtcManagers.push(rtcManager);
164
+ }
165
+ removeRtcManager(rtcManager) {
166
+ this._rtcManagers = this._rtcManagers.filter((r) => r !== rtcManager);
167
+ }
168
+ getCameraDeviceId() {
169
+ var _a;
170
+ return (_a = this.stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
171
+ }
172
+ getMicrophoneDeviceId() {
173
+ var _a;
174
+ return (_a = this.stream.getAudioTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
175
+ }
176
+ isCameraEnabled() {
177
+ var _a;
178
+ return !!((_a = this.stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.enabled);
179
+ }
180
+ isMicrophoneEnabled() {
181
+ var _a;
182
+ return !!((_a = this.stream.getAudioTracks()[0]) === null || _a === void 0 ? void 0 : _a.enabled);
183
+ }
184
+ toggleCameraEnabled(enabled) {
185
+ const videoTrack = this.stream.getVideoTracks()[0];
186
+ if (!videoTrack) {
187
+ return;
188
+ }
189
+ const newValue = enabled !== null && enabled !== void 0 ? enabled : !videoTrack.enabled;
190
+ videoTrack.enabled = newValue;
191
+ this.dispatchEvent(new CustomEvent("camera_enabled", { detail: { enabled: newValue } }));
192
+ }
193
+ toggleMichrophoneEnabled(enabled) {
194
+ const audioTrack = this.stream.getAudioTracks()[0];
195
+ if (!audioTrack) {
196
+ return;
197
+ }
198
+ const newValue = enabled !== null && enabled !== void 0 ? enabled : !audioTrack.enabled;
199
+ audioTrack.enabled = newValue;
200
+ this.dispatchEvent(new CustomEvent("microphone_enabled", { detail: { enabled: newValue } }));
201
+ }
202
+ setCameraDevice(deviceId) {
203
+ return __awaiter(this, void 0, void 0, function* () {
204
+ const newStream = yield navigator.mediaDevices.getUserMedia({ video: { deviceId } });
205
+ const newVideoTrack = newStream.getVideoTracks()[0];
206
+ if (newVideoTrack) {
207
+ const oldVideoTrack = this.stream.getVideoTracks()[0];
208
+ newVideoTrack.enabled = oldVideoTrack.enabled;
209
+ oldVideoTrack === null || oldVideoTrack === void 0 ? void 0 : oldVideoTrack.stop();
210
+ this._rtcManagers.forEach((rtcManager) => {
211
+ rtcManager.replaceTrack(oldVideoTrack, newVideoTrack);
212
+ });
213
+ this.stream.removeTrack(oldVideoTrack);
214
+ this.stream.addTrack(newVideoTrack);
215
+ }
216
+ this.dispatchEvent(new CustomEvent("stream_updated", {
217
+ detail: { stream: this.stream },
218
+ }));
219
+ });
220
+ }
221
+ setMicrophoneDevice(deviceId) {
222
+ return __awaiter(this, void 0, void 0, function* () {
223
+ const newStream = yield navigator.mediaDevices.getUserMedia({ audio: { deviceId } });
224
+ const newAudioTrack = newStream.getAudioTracks()[0];
225
+ const oldAudioTrack = this.stream.getAudioTracks()[0];
226
+ if (oldAudioTrack) {
227
+ newAudioTrack.enabled = oldAudioTrack.enabled;
228
+ oldAudioTrack.stop();
229
+ this.stream.removeTrack(oldAudioTrack);
230
+ }
231
+ this._rtcManagers.forEach((rtcManager) => {
232
+ rtcManager.replaceTrack(oldAudioTrack, newAudioTrack);
233
+ });
234
+ this.stream.addTrack(newAudioTrack);
235
+ this.dispatchEvent(new CustomEvent("stream_updated", {
236
+ detail: { stream: this.stream },
237
+ }));
238
+ });
239
+ }
240
+ _updateDeviceList() {
241
+ return __awaiter(this, void 0, void 0, function* () {
242
+ try {
243
+ const devices = yield navigator.mediaDevices.enumerateDevices();
244
+ this.dispatchEvent(new CustomEvent("device_list_updated", {
245
+ detail: {
246
+ cameraDevices: devices.filter((d) => d.kind === "videoinput"),
247
+ microphoneDevices: devices.filter((d) => d.kind === "audioinput"),
248
+ speakerDevices: devices.filter((d) => d.kind === "audiooutput"),
249
+ },
250
+ }));
251
+ }
252
+ catch (error) {
253
+ this.dispatchEvent(new CustomEvent("device_list_update_error", {
254
+ detail: {
255
+ error,
256
+ },
257
+ }));
258
+ throw error;
259
+ }
260
+ });
261
+ }
262
+ start() {
263
+ return __awaiter(this, void 0, void 0, function* () {
264
+ const newStream = yield navigator.mediaDevices.getUserMedia(this._constraints);
265
+ newStream.getTracks().forEach((t) => this.stream.addTrack(t));
266
+ this._updateDeviceList();
267
+ this.dispatchEvent(new CustomEvent("stream_updated", {
268
+ detail: { stream: this.stream },
269
+ }));
270
+ return this.stream;
271
+ });
272
+ }
273
+ stop() {
274
+ var _a;
275
+ (_a = this.stream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => {
276
+ t.stop();
277
+ });
278
+ }
279
+ }
280
+
281
+ const initialState$1 = {
282
+ cameraDeviceError: null,
283
+ cameraDevices: [],
284
+ isSettingCameraDevice: false,
285
+ isSettingMicrophoneDevice: false,
286
+ isStarting: false,
287
+ microphoneDeviceError: null,
288
+ microphoneDevices: [],
289
+ speakerDevices: [],
290
+ startError: null,
291
+ };
292
+ function reducer$1(state, action) {
293
+ switch (action.type) {
294
+ case "DEVICE_LIST_UPDATED":
295
+ return Object.assign(Object.assign({}, state), action.payload);
296
+ case "LOCAL_STREAM_UPDATED":
297
+ return Object.assign(Object.assign({}, state), { currentCameraDeviceId: action.payload.currentCameraDeviceId, currentMicrophoneDeviceId: action.payload.currentMicrophoneDeviceId, localStream: action.payload.stream });
298
+ case "SET_CAMERA_DEVICE":
299
+ return Object.assign(Object.assign({}, state), { cameraDeviceError: null, isSettingCameraDevice: true });
300
+ case "SET_CAMERA_DEVICE_COMPLETE":
301
+ return Object.assign(Object.assign({}, state), { isSettingCameraDevice: false });
302
+ case "SET_CAMERA_DEVICE_ERROR":
303
+ return Object.assign(Object.assign({}, state), { cameraDeviceError: action.payload, isSettingCameraDevice: false });
304
+ case "SET_MICROPHONE_DEVICE":
305
+ return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: true, microphoneDeviceError: null });
306
+ case "SET_MICROPHONE_DEVICE_COMPLETE":
307
+ return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: false });
308
+ case "SET_MICROPHONE_DEVICE_ERROR":
309
+ return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: false, microphoneDeviceError: action.payload });
310
+ case "START":
311
+ return Object.assign(Object.assign({}, state), { isStarting: true, startError: null });
312
+ case "START_COMPLETE":
313
+ return Object.assign(Object.assign({}, state), { isStarting: false });
314
+ case "START_ERROR":
315
+ return Object.assign(Object.assign({}, state), { isStarting: false, startError: action.payload });
316
+ default:
317
+ return state;
318
+ }
319
+ }
320
+ function useLocalMedia(constraints = { audio: true, video: true }) {
321
+ const [localMedia] = useState(() => new LocalMedia(constraints));
322
+ const [state, dispatch] = useReducer(reducer$1, initialState$1);
323
+ useEffect(() => {
324
+ localMedia.addEventListener("device_list_updated", (e) => {
325
+ const { cameraDevices, microphoneDevices, speakerDevices } = e.detail;
326
+ dispatch({ type: "DEVICE_LIST_UPDATED", payload: { cameraDevices, microphoneDevices, speakerDevices } });
327
+ });
328
+ localMedia.addEventListener("stream_updated", (e) => {
329
+ const { stream } = e.detail;
330
+ dispatch({
331
+ type: "LOCAL_STREAM_UPDATED",
332
+ payload: {
333
+ stream,
334
+ currentCameraDeviceId: localMedia.getCameraDeviceId(),
335
+ currentMicrophoneDeviceId: localMedia.getMicrophoneDeviceId(),
336
+ },
337
+ });
338
+ });
339
+ const start = () => __awaiter(this, void 0, void 0, function* () {
340
+ dispatch({ type: "START" });
341
+ try {
342
+ yield localMedia.start();
343
+ dispatch({ type: "START_COMPLETE" });
344
+ }
345
+ catch (error) {
346
+ dispatch({ type: "START_ERROR", payload: error });
347
+ }
348
+ });
349
+ start();
350
+ // Perform cleanup on unmount
351
+ return () => {
352
+ localMedia.stop();
353
+ };
354
+ }, []);
355
+ return {
356
+ state,
357
+ actions: {
358
+ setCameraDevice: (...args) => __awaiter(this, void 0, void 0, function* () {
359
+ dispatch({ type: "SET_CAMERA_DEVICE" });
360
+ try {
361
+ yield localMedia.setCameraDevice(...args);
362
+ dispatch({ type: "SET_CAMERA_DEVICE_COMPLETE" });
363
+ }
364
+ catch (error) {
365
+ dispatch({ type: "SET_CAMERA_DEVICE_ERROR", payload: error });
366
+ }
367
+ }),
368
+ setMicrophoneDevice: (...args) => __awaiter(this, void 0, void 0, function* () {
369
+ dispatch({ type: "SET_MICROPHONE_DEVICE" });
370
+ try {
371
+ yield localMedia.setMicrophoneDevice(...args);
372
+ dispatch({ type: "SET_MICROPHONE_DEVICE_COMPLETE" });
373
+ }
374
+ catch (error) {
375
+ dispatch({ type: "SET_MICROPHONE_DEVICE_ERROR", payload: error });
376
+ }
377
+ }),
378
+ toggleCameraEnabled: (...args) => {
379
+ return localMedia.toggleCameraEnabled(...args);
380
+ },
381
+ toggleMicrophoneEnabled: (...args) => {
382
+ return localMedia.toggleMichrophoneEnabled(...args);
383
+ },
384
+ },
385
+ _ref: localMedia,
386
+ };
387
+ }
388
+
153
389
  const EVENTS = {
154
390
  CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
155
391
  STREAM_ADDED: "stream_added",
@@ -280,10 +516,10 @@ class ServerSocket {
280
516
  }
281
517
 
282
518
  this._socket = io(hostName, options);
283
- this._socket.on("reconnect", () => {
519
+ this._socket.io.on("reconnect", () => {
284
520
  this._socket.sendBuffer = [];
285
521
  });
286
- this._socket.on("reconnect_attempt", () => {
522
+ this._socket.io.on("reconnect_attempt", () => {
287
523
  if (this._wasConnectedUsingWebsocket) {
288
524
  this._socket.io.opts.transports = ["websocket"];
289
525
  // only fallback to polling if not safari
@@ -341,6 +577,10 @@ class ServerSocket {
341
577
  );
342
578
  }
343
579
 
580
+ getManager() {
581
+ return this._socket.io;
582
+ }
583
+
344
584
  isConnecting() {
345
585
  return this._socket && this._socket.connecting;
346
586
  }
@@ -1974,10 +2214,11 @@ class VegaParser {
1974
2214
  }
1975
2215
 
1976
2216
  class VegaConnection extends EventEmitter {
1977
- constructor(wsUrl, logger) {
2217
+ constructor(wsUrl, logger, protocol = "whereby-sfu#v4") {
1978
2218
  super();
1979
2219
 
1980
2220
  this.wsUrl = wsUrl;
2221
+ this.protocol = protocol;
1981
2222
  this.logger = logger;
1982
2223
 
1983
2224
  // This is the map of sent requests that are waiting for a response
@@ -1986,7 +2227,7 @@ class VegaConnection extends EventEmitter {
1986
2227
  }
1987
2228
 
1988
2229
  _setupSocket() {
1989
- this.socket = new WebSocket(this.wsUrl, "whereby-sfu#v4");
2230
+ this.socket = new WebSocket(this.wsUrl, this.protocol);
1990
2231
  this.socket.onopen = this._onOpen.bind(this);
1991
2232
  this.socket.onmessage = this._onMessage.bind(this);
1992
2233
  this.socket.onclose = this._onClose.bind(this);
@@ -2006,7 +2247,9 @@ class VegaConnection extends EventEmitter {
2006
2247
  }
2007
2248
 
2008
2249
  close() {
2009
- this.socket?.close();
2250
+ if (!this.socket) return;
2251
+
2252
+ this.socket.close();
2010
2253
  }
2011
2254
 
2012
2255
  _onOpen() {
@@ -2018,11 +2261,15 @@ class VegaConnection extends EventEmitter {
2018
2261
  _onMessage(event) {
2019
2262
  const socketMessage = VegaParser.parse(event.data);
2020
2263
 
2264
+ if (!socketMessage) {
2265
+ return this.logger.log("VegaConnectionManager: Received invalid message", event.data);
2266
+ }
2267
+
2021
2268
  this.logger.log("VegaConnectionManager: Received message", socketMessage);
2022
2269
 
2023
- if (socketMessage?.response) {
2270
+ if (socketMessage.response) {
2024
2271
  this._handleResponse(socketMessage);
2025
- } else if (socketMessage?.message) {
2272
+ } else if (socketMessage.message) {
2026
2273
  this.emit("message", socketMessage);
2027
2274
  }
2028
2275
  }
@@ -2129,7 +2376,7 @@ const VIDEO_SETTINGS_VP9 = {
2129
2376
  codecOptions: {
2130
2377
  videoGoogleStartBitrate: 500,
2131
2378
  },
2132
- encodings: [{ scalabilityMode: "S3T3_KEY", networkPriority: "high" }],
2379
+ encodings: [{ scalabilityMode: "L3T2_KEY", networkPriority: "high" }],
2133
2380
  };
2134
2381
 
2135
2382
  const SCREEN_SHARE_SETTINGS = {
@@ -2138,13 +2385,13 @@ const SCREEN_SHARE_SETTINGS = {
2138
2385
 
2139
2386
  const SCREEN_SHARE_SIMULCAST_SETTINGS = {
2140
2387
  encodings: [
2141
- { dtx: true, maxBitrate: 500000 },
2142
- { dtx: true, maxBitrate: 1500000 },
2388
+ { scaleResolutionDownBy: 2, dtx: true, maxBitrate: 500000 },
2389
+ { scaleResolutionDownBy: 1, dtx: true, maxBitrate: 1500000 },
2143
2390
  ],
2144
2391
  };
2145
2392
 
2146
2393
  const SCREEN_SHARE_SETTINGS_VP9 = {
2147
- encodings: [{ scalabilityMode: "S3T3", dtx: true, networkPriority: "high" }],
2394
+ encodings: [{ scalabilityMode: "L1T1", dtx: true, networkPriority: "high" }],
2148
2395
  };
2149
2396
 
2150
2397
  const getMediaSettings = (kind, isScreenShare, features) => {
@@ -2160,8 +2407,8 @@ const getMediaSettings = (kind, isScreenShare, features) => {
2160
2407
 
2161
2408
  return SCREEN_SHARE_SETTINGS;
2162
2409
  } else {
2163
- if (lowDataModeEnabled) return VIDEO_SETTINGS_SD;
2164
2410
  if (vp9On) return VIDEO_SETTINGS_VP9;
2411
+ if (lowDataModeEnabled) return VIDEO_SETTINGS_SD;
2165
2412
 
2166
2413
  return VIDEO_SETTINGS_HD;
2167
2414
  }
@@ -5020,7 +5267,7 @@ const SIGNAL_BASE_URL = "wss://signal.appearin.net";
5020
5267
  const NON_PERSON_ROLES = ["recorder", "streamer"];
5021
5268
  function createSocket() {
5022
5269
  const parsedUrl = new URL(SIGNAL_BASE_URL);
5023
- const path = `${parsedUrl.pathname.replace(/^\/$/, "")}/protocol/socket.io/v1`;
5270
+ const path = `${parsedUrl.pathname.replace(/^\/$/, "")}/protocol/socket.io/v4`;
5024
5271
  const SOCKET_HOST = parsedUrl.origin;
5025
5272
  const socketConf = {
5026
5273
  host: SOCKET_HOST,
@@ -5036,11 +5283,12 @@ const noop = () => {
5036
5283
  };
5037
5284
  const TypedEventTarget = EventTarget;
5038
5285
  class RoomConnection extends TypedEventTarget {
5039
- constructor(roomUrl, { displayName, localMediaConstraints, localStream, logger }) {
5286
+ constructor(roomUrl, { displayName, localMediaConstraints, logger, localMedia }) {
5040
5287
  super();
5041
5288
  this.localParticipant = null;
5042
5289
  this.remoteParticipants = [];
5043
5290
  this.roomConnectionState = "";
5291
+ this._ownsLocalMedia = false;
5044
5292
  this.roomUrl = new URL(roomUrl); // Throw if invalid Whereby room url
5045
5293
  this.logger = logger || {
5046
5294
  debug: noop,
@@ -5049,10 +5297,19 @@ class RoomConnection extends TypedEventTarget {
5049
5297
  warn: noop,
5050
5298
  };
5051
5299
  this.displayName = displayName;
5052
- this.localStream = localStream;
5053
5300
  this.localMediaConstraints = localMediaConstraints;
5054
5301
  const urls = fromLocation({ host: this.roomUrl.host });
5055
- // Initialize services
5302
+ // Set up local media
5303
+ if (localMedia) {
5304
+ this.localMedia = localMedia;
5305
+ }
5306
+ else if (localMediaConstraints) {
5307
+ this.localMedia = new LocalMedia(localMediaConstraints);
5308
+ this._ownsLocalMedia = true;
5309
+ }
5310
+ else {
5311
+ throw new Error("Missing constraints");
5312
+ }
5056
5313
  this.credentialsService = CredentialsService.create({ baseUrl: API_BASE_URL });
5057
5314
  this.apiClient = new ApiClient({
5058
5315
  fetchDeviceCredentials: this.credentialsService.getCredentials.bind(this.credentialsService),
@@ -5078,6 +5335,15 @@ class RoomConnection extends TypedEventTarget {
5078
5335
  this.signalSocket.on("audio_enabled", this._handleClientAudioEnabled.bind(this));
5079
5336
  this.signalSocket.on("video_enabled", this._handleClientVideoEnabled.bind(this));
5080
5337
  this.signalSocket.on("client_metadata_received", this._handleClientMetadataReceived.bind(this));
5338
+ // Set up local media listeners
5339
+ this.localMedia.addEventListener("camera_enabled", (e) => {
5340
+ const { enabled } = e.detail;
5341
+ this.signalSocket.emit("enable_video", { enabled });
5342
+ });
5343
+ this.localMedia.addEventListener("microphone_enabled", (e) => {
5344
+ const { enabled } = e.detail;
5345
+ this.signalSocket.emit("enable_audio", { enabled });
5346
+ });
5081
5347
  }
5082
5348
  _handleNewClient({ client }) {
5083
5349
  if (NON_PERSON_ROLES.includes(client.role.roleName)) {
@@ -5135,10 +5401,11 @@ class RoomConnection extends TypedEventTarget {
5135
5401
  }
5136
5402
  }
5137
5403
  _handleRtcManagerCreated({ rtcManager }) {
5138
- var _a, _b, _c;
5404
+ var _a;
5139
5405
  this.rtcManager = rtcManager;
5140
- if (this.localStream) {
5141
- (_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)));
5406
+ this.localMedia.addRtcManager(rtcManager);
5407
+ if (this.localMedia.stream) {
5408
+ (_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.addNewStream("0", this.localMedia.stream, !this.localMedia.isMicrophoneEnabled(), !this.localMedia.isCameraEnabled());
5142
5409
  }
5143
5410
  }
5144
5411
  _handleAcceptStreams(remoteParticipants) {
@@ -5163,8 +5430,6 @@ class RoomConnection extends TypedEventTarget {
5163
5430
  if (!newState) {
5164
5431
  return;
5165
5432
  }
5166
- // #endregion
5167
- // #region doAcceptStreams
5168
5433
  if (newState === "to_accept" ||
5169
5434
  (newState === "new_accept" && shouldAcceptNewClients) ||
5170
5435
  (newState === "old_accept" && !shouldAcceptNewClients)) {
@@ -5188,7 +5453,6 @@ class RoomConnection extends TypedEventTarget {
5188
5453
  else ;
5189
5454
  // Update stream state
5190
5455
  participant.updateStreamState(streamId, streamState.replace(/to_|new_|old_/, "done_"));
5191
- // #endregion
5192
5456
  });
5193
5457
  });
5194
5458
  }
@@ -5200,9 +5464,6 @@ class RoomConnection extends TypedEventTarget {
5200
5464
  }
5201
5465
  this.dispatchEvent(new CustomEvent("participant_stream_added", { detail: { participantId: clientId, stream, streamId } }));
5202
5466
  }
5203
- /**
5204
- * Public API
5205
- */
5206
5467
  join() {
5207
5468
  return __awaiter(this, void 0, void 0, function* () {
5208
5469
  if (["connected", "connecting"].includes(this.roomConnectionState)) {
@@ -5211,24 +5472,14 @@ class RoomConnection extends TypedEventTarget {
5211
5472
  }
5212
5473
  this.logger.log("Joining room");
5213
5474
  this.roomConnectionState = "connecting";
5214
- if (!this.localStream && this.localMediaConstraints) {
5215
- const localStream = yield navigator.mediaDevices.getUserMedia(this.localMediaConstraints);
5216
- this.localStream = localStream;
5217
- }
5218
- const organization = yield this.organizationServiceCache.fetchOrganization();
5219
- if (!organization) {
5220
- throw new Error("Invalid room url");
5475
+ if (this._ownsLocalMedia) {
5476
+ yield this.localMedia.start();
5221
5477
  }
5222
- // TODO: Get room permissions
5223
- // TODO: Get room features
5224
5478
  const webrtcProvider = {
5225
- getMediaConstraints: () => {
5226
- var _a, _b;
5227
- return ({
5228
- audio: !!((_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().find((t) => t.enabled)),
5229
- video: !!((_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getVideoTracks().find((t) => t.enabled)),
5230
- });
5231
- },
5479
+ getMediaConstraints: () => ({
5480
+ audio: this.localMedia.isMicrophoneEnabled(),
5481
+ video: this.localMedia.isCameraEnabled(),
5482
+ }),
5232
5483
  deferrable(clientId) {
5233
5484
  return !clientId;
5234
5485
  },
@@ -5249,20 +5500,24 @@ class RoomConnection extends TypedEventTarget {
5249
5500
  simulcastScreenshareOn: false,
5250
5501
  },
5251
5502
  });
5503
+ const organization = yield this.organizationServiceCache.fetchOrganization();
5504
+ if (!organization) {
5505
+ throw new Error("Invalid room url");
5506
+ }
5252
5507
  // Identify device on signal connection
5253
5508
  const deviceCredentials = yield this.credentialsService.getCredentials();
5509
+ this.signalSocket.connect();
5254
5510
  // TODO: Handle connection and failed connection properly
5255
- setTimeout(() => {
5511
+ this.signalSocket.on("connect", () => {
5256
5512
  this.logger.log("Connected to signal socket");
5257
5513
  this.signalSocket.emit("identify_device", { deviceCredentials });
5258
- }, 2000);
5514
+ });
5259
5515
  this.signalSocket.once("device_identified", () => {
5260
- var _a, _b;
5261
5516
  this.signalSocket.emit("join_room", {
5262
5517
  avatarUrl: null,
5263
5518
  config: {
5264
- isAudioEnabled: !!((_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().find((t) => t.enabled)),
5265
- isVideoEnabled: !!((_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getVideoTracks().find((t) => t.enabled)),
5519
+ isAudioEnabled: this.localMedia.isMicrophoneEnabled(),
5520
+ isVideoEnabled: this.localMedia.isCameraEnabled(),
5266
5521
  },
5267
5522
  deviceCapabilities: { canScreenshare: true },
5268
5523
  displayName: this.displayName,
@@ -5273,7 +5528,7 @@ class RoomConnection extends TypedEventTarget {
5273
5528
  roomKey: null,
5274
5529
  roomName: this.roomUrl.pathname,
5275
5530
  selfId: "",
5276
- userAgent: `browser-sdk:${sdkVersion }`
5531
+ userAgent: `browser-sdk:${sdkVersion }`,
5277
5532
  });
5278
5533
  });
5279
5534
  this.signalSocket.once("room_joined", (res) => {
@@ -5281,7 +5536,7 @@ class RoomConnection extends TypedEventTarget {
5281
5536
  const localClient = clients.find((c) => c.id === selfId);
5282
5537
  if (!localClient)
5283
5538
  throw new Error("Missing local client");
5284
- this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localStream }));
5539
+ this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localMedia.stream || undefined }));
5285
5540
  this.remoteParticipants = clients
5286
5541
  .filter((c) => c.id !== selfId)
5287
5542
  .map((c) => new RemoteParticipant(Object.assign(Object.assign({}, c), { newJoiner: false })));
@@ -5301,6 +5556,14 @@ class RoomConnection extends TypedEventTarget {
5301
5556
  }
5302
5557
  leave() {
5303
5558
  return new Promise((resolve) => {
5559
+ if (this._ownsLocalMedia) {
5560
+ this.localMedia.stop();
5561
+ }
5562
+ if (this.rtcManager) {
5563
+ this.localMedia.removeRtcManager(this.rtcManager);
5564
+ this.rtcManager.disconnectAll();
5565
+ this.rtcManager = undefined;
5566
+ }
5304
5567
  if (!this.signalSocket) {
5305
5568
  return resolve();
5306
5569
  }
@@ -5315,30 +5578,6 @@ class RoomConnection extends TypedEventTarget {
5315
5578
  });
5316
5579
  });
5317
5580
  }
5318
- toggleCamera(enabled) {
5319
- var _a;
5320
- const localVideoTrack = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks()[0];
5321
- if (!localVideoTrack) {
5322
- this.logger.log("Tried toggling non-existing video track");
5323
- return;
5324
- }
5325
- // TODO: Do stopOrResumeVideo
5326
- const newValue = enabled !== null && enabled !== void 0 ? enabled : !localVideoTrack.enabled;
5327
- localVideoTrack.enabled = newValue;
5328
- this.signalSocket.emit("enable_video", { enabled: newValue });
5329
- }
5330
- toggleMicrophone(enabled) {
5331
- var _a;
5332
- const localAudioTrack = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
5333
- if (!localAudioTrack) {
5334
- this.logger.log("Tried toggling non-existing audio track");
5335
- return;
5336
- }
5337
- // TODO: Do stopOrResumeAudio
5338
- const newValue = enabled !== null && enabled !== void 0 ? enabled : !localAudioTrack.enabled;
5339
- localAudioTrack.enabled = newValue;
5340
- this.signalSocket.emit("enable_audio", { enabled: newValue });
5341
- }
5342
5581
  setDisplayName(displayName) {
5343
5582
  this.signalSocket.emit("send_client_metadata", {
5344
5583
  type: "UserData",
@@ -5349,6 +5588,11 @@ class RoomConnection extends TypedEventTarget {
5349
5588
  }
5350
5589
  }
5351
5590
 
5591
+ const initialState = {
5592
+ isJoining: false,
5593
+ joinError: null,
5594
+ remoteParticipants: [],
5595
+ };
5352
5596
  function updateParticipant(remoteParticipants, participantId, updates) {
5353
5597
  const existingParticipant = remoteParticipants.find((p) => p.id === participantId);
5354
5598
  if (!existingParticipant) {
@@ -5392,18 +5636,14 @@ function reducer(state, action) {
5392
5636
  default:
5393
5637
  throw state;
5394
5638
  }
5395
- }
5396
-
5639
+ }
5397
5640
  function useRoomConnection(roomUrl, roomConnectionOptions) {
5398
- const [roomConnection, setRoomConnection] = useState(null);
5399
- const [state, dispatch] = useReducer(reducer, { remoteParticipants: [] });
5400
- useEffect(() => {
5401
- setRoomConnection(new RoomConnection(roomUrl, roomConnectionOptions));
5402
- }, [roomUrl]);
5641
+ const [roomConnection] = useState(() => {
5642
+ var _a;
5643
+ 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 }));
5644
+ });
5645
+ const [state, dispatch] = useReducer(reducer, initialState);
5403
5646
  useEffect(() => {
5404
- if (!roomConnection) {
5405
- return;
5406
- }
5407
5647
  roomConnection.addEventListener("participant_audio_enabled", (e) => {
5408
5648
  const { participantId, isAudioEnabled } = e.detail;
5409
5649
  dispatch({ type: "PARTICIPANT_AUDIO_ENABLED", payload: { participantId, isAudioEnabled } });
@@ -5436,27 +5676,28 @@ function useRoomConnection(roomUrl, roomConnectionOptions) {
5436
5676
  return () => {
5437
5677
  roomConnection.leave();
5438
5678
  };
5439
- }, [roomConnection]);
5440
- return [
5679
+ }, []);
5680
+ return {
5441
5681
  state,
5442
- {
5682
+ actions: {
5443
5683
  toggleCamera: (enabled) => {
5444
- roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.toggleCamera(enabled);
5684
+ roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.localMedia.toggleCameraEnabled(enabled);
5445
5685
  },
5446
5686
  toggleMicrophone: (enabled) => {
5447
- roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.toggleMicrophone(enabled);
5687
+ roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.localMedia.toggleMichrophoneEnabled(enabled);
5448
5688
  },
5449
5689
  setDisplayName: (displayName) => {
5450
5690
  roomConnection === null || roomConnection === void 0 ? void 0 : roomConnection.setDisplayName(displayName);
5451
5691
  dispatch({ type: "LOCAL_CLIENT_DISPLAY_NAME_CHANGED", payload: { displayName } });
5452
5692
  },
5453
5693
  },
5454
- {
5694
+ components: {
5455
5695
  VideoView,
5456
5696
  },
5457
- ];
5697
+ _ref: roomConnection,
5698
+ };
5458
5699
  }
5459
5700
 
5460
- const sdkVersion = "2.0.0-alpha5";
5701
+ const sdkVersion = "2.0.0-alpha7";
5461
5702
 
5462
- export { sdkVersion, useRoomConnection };
5703
+ export { VideoView, sdkVersion, useLocalMedia, useRoomConnection };