@whereby.com/browser-sdk 2.0.0-beta3 → 2.0.0-beta4
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 +1 -5
- package/dist/embed/index.d.ts +20 -6
- package/dist/embed/index.esm.js +55 -31
- package/dist/react/index.d.ts +724 -292
- package/dist/react/index.esm.js +2597 -2036
- package/dist/v2-beta4.js +16 -0
- package/package.json +5 -1
- package/dist/v2-beta3.js +0 -15
package/dist/react/index.esm.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default, { useRef, useEffect, useState, useCallback } from 'react';
|
|
3
|
+
import { createListenerMiddleware, createSlice, createAsyncThunk, createAction, createSelector, isAnyOf, combineReducers, configureStore } from '@reduxjs/toolkit';
|
|
3
4
|
import { io } from 'socket.io-client';
|
|
5
|
+
import adapter from 'webrtc-adapter';
|
|
4
6
|
import SDPUtils from 'sdp';
|
|
5
7
|
import * as sdpTransform from 'sdp-transform';
|
|
6
8
|
import { v4 } from 'uuid';
|
|
@@ -116,9 +118,520 @@ var VideoView = (_a) => {
|
|
|
116
118
|
videoEl.current.muted = Boolean(muted);
|
|
117
119
|
}
|
|
118
120
|
}, [muted, stream, videoEl]);
|
|
119
|
-
return (
|
|
121
|
+
return (React__default.createElement("video", Object.assign({ ref: videoEl, autoPlay: true, playsInline: true }, rest, { style: Object.assign({ transform: mirror ? "scaleX(-1)" : "none", width: "100%", height: "100%" }, rest.style) })));
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const listenerMiddleware = createListenerMiddleware();
|
|
125
|
+
const startAppListening = listenerMiddleware.startListening;
|
|
126
|
+
/**
|
|
127
|
+
* Creates a reactor that will be called whenever the provided selectors change.
|
|
128
|
+
* Every reactor needs to update a piece of state that it depends on, to avoid infinite loops.
|
|
129
|
+
* example:
|
|
130
|
+
* ```ts
|
|
131
|
+
* createReactor(
|
|
132
|
+
* [selectAppWantsToJoin, selectDeviceCredentialsRaw],
|
|
133
|
+
* ({ dispatch }, wantsToJoin, deviceCredentialsRaw) => {
|
|
134
|
+
* if (wantsToJoin && deviceCredentialsRaw.data) {
|
|
135
|
+
* dispatch(doSignalIdentifyDevice({ deviceCredentials: deviceCredentialsRaw.data }));
|
|
136
|
+
* }
|
|
137
|
+
* });
|
|
138
|
+
* ```
|
|
139
|
+
* @param selectors. The selectors to be used to check if the state has changed.
|
|
140
|
+
* @param callback. The callback to be called on every action. The first argument is the listenerApi, the second argument is the result of the selectors.
|
|
141
|
+
* @returns The unsubscribe function.
|
|
142
|
+
*/
|
|
143
|
+
const createReactor = (selectors, callback) => {
|
|
144
|
+
return startAppListening({
|
|
145
|
+
predicate: (_, currentState, previousState) => {
|
|
146
|
+
const previousValues = selectors.map((selector) => selector(previousState));
|
|
147
|
+
const currentValues = selectors.map((selector) => selector(currentState));
|
|
148
|
+
return previousValues.some((previousValue, index) => previousValue !== currentValues[index]);
|
|
149
|
+
},
|
|
150
|
+
effect: (action, { dispatch, getState, extra }) => {
|
|
151
|
+
const selectorResults = selectors.map((selector) => selector(getState()));
|
|
152
|
+
callback({
|
|
153
|
+
dispatch,
|
|
154
|
+
getState,
|
|
155
|
+
extra,
|
|
156
|
+
}, ...selectorResults);
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const initialState$g = {
|
|
162
|
+
wantsToJoin: false,
|
|
163
|
+
roomName: null,
|
|
164
|
+
roomKey: null,
|
|
165
|
+
roomUrl: null,
|
|
166
|
+
displayName: null,
|
|
167
|
+
sdkVersion: null,
|
|
168
|
+
externalId: null,
|
|
169
|
+
};
|
|
170
|
+
const appSlice = createSlice({
|
|
171
|
+
name: "app",
|
|
172
|
+
initialState: initialState$g,
|
|
173
|
+
reducers: {
|
|
174
|
+
doAppJoin: (state, action) => {
|
|
175
|
+
const url = new URL(action.payload.roomUrl);
|
|
176
|
+
return Object.assign(Object.assign(Object.assign({}, state), action.payload), { roomName: url.pathname, wantsToJoin: true });
|
|
177
|
+
},
|
|
178
|
+
appLeft: (state) => {
|
|
179
|
+
return Object.assign(Object.assign({}, state), { wantsToJoin: false });
|
|
180
|
+
},
|
|
181
|
+
setRoomKey: (state, action) => {
|
|
182
|
+
return Object.assign(Object.assign({}, state), { roomKey: action.payload });
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
/**
|
|
187
|
+
* Action creators
|
|
188
|
+
*/
|
|
189
|
+
const { doAppJoin, appLeft, setRoomKey } = appSlice.actions;
|
|
190
|
+
const selectAppWantsToJoin = (state) => state.app.wantsToJoin;
|
|
191
|
+
const selectAppRoomName = (state) => state.app.roomName;
|
|
192
|
+
const selectAppRoomUrl = (state) => state.app.roomUrl;
|
|
193
|
+
const selectAppRoomKey = (state) => state.app.roomKey;
|
|
194
|
+
const selectAppDisplayName = (state) => state.app.displayName;
|
|
195
|
+
const selectAppSdkVersion = (state) => state.app.sdkVersion;
|
|
196
|
+
const selectAppExternalId = (state) => state.app.externalId;
|
|
197
|
+
|
|
198
|
+
function createAppAsyncThunk(typePrefix, payloadCreator) {
|
|
199
|
+
return createAsyncThunk(typePrefix, payloadCreator);
|
|
200
|
+
}
|
|
201
|
+
function createAppThunk(thunk) {
|
|
202
|
+
return thunk;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function createSignalEventAction(name) {
|
|
206
|
+
return createAction(`signalConnection/event/${name}`);
|
|
207
|
+
}
|
|
208
|
+
const signalEvents = {
|
|
209
|
+
audioEnabled: createSignalEventAction("audioEnabled"),
|
|
210
|
+
chatMessage: createSignalEventAction("chatMessage"),
|
|
211
|
+
clientLeft: createSignalEventAction("clientLeft"),
|
|
212
|
+
clientMetadataReceived: createSignalEventAction("clientMetadataReceived"),
|
|
213
|
+
cloudRecordingStarted: createSignalEventAction("cloudRecordingStarted"),
|
|
214
|
+
cloudRecordingStopped: createSignalEventAction("cloudRecordingStopped"),
|
|
215
|
+
disconnect: createSignalEventAction("disconnect"),
|
|
216
|
+
knockerLeft: createSignalEventAction("knockerLeft"),
|
|
217
|
+
knockHandled: createSignalEventAction("knockHandled"),
|
|
218
|
+
newClient: createSignalEventAction("newClient"),
|
|
219
|
+
roomJoined: createSignalEventAction("roomJoined"),
|
|
220
|
+
roomKnocked: createSignalEventAction("roomKnocked"),
|
|
221
|
+
screenshareStarted: createSignalEventAction("screenshareStarted"),
|
|
222
|
+
screenshareStopped: createSignalEventAction("screenshareStopped"),
|
|
223
|
+
streamingStopped: createSignalEventAction("streamingStopped"),
|
|
224
|
+
videoEnabled: createSignalEventAction("videoEnabled"),
|
|
120
225
|
};
|
|
121
226
|
|
|
227
|
+
const initialState$f = {
|
|
228
|
+
isFetching: false,
|
|
229
|
+
data: null,
|
|
230
|
+
};
|
|
231
|
+
const deviceCredentialsSlice = createSlice({
|
|
232
|
+
name: "deviceCredentials",
|
|
233
|
+
initialState: initialState$f,
|
|
234
|
+
reducers: {},
|
|
235
|
+
extraReducers: (builder) => {
|
|
236
|
+
builder.addCase(doGetDeviceCredentials.pending, (state) => {
|
|
237
|
+
return Object.assign(Object.assign({}, state), { isFetching: true });
|
|
238
|
+
});
|
|
239
|
+
builder.addCase(doGetDeviceCredentials.fulfilled, (state, action) => {
|
|
240
|
+
return Object.assign(Object.assign({}, state), { isFetching: false, data: action.payload });
|
|
241
|
+
});
|
|
242
|
+
builder.addCase(doGetDeviceCredentials.rejected, (state) => {
|
|
243
|
+
// not handled in the pwa either.
|
|
244
|
+
return Object.assign(Object.assign({}, state), { isFetching: true });
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
/**
|
|
249
|
+
* Action creators
|
|
250
|
+
*/
|
|
251
|
+
const doGetDeviceCredentials = createAppAsyncThunk("deviceCredentials/doGetDeviceCredentials", (payload, { extra }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
252
|
+
try {
|
|
253
|
+
const deviceCredentials = yield extra.services.credentialsService.getCredentials();
|
|
254
|
+
return deviceCredentials;
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
console.error(error);
|
|
258
|
+
}
|
|
259
|
+
}));
|
|
260
|
+
/**
|
|
261
|
+
* Selectors
|
|
262
|
+
*/
|
|
263
|
+
const selectDeviceCredentialsRaw = (state) => state.deviceCredentials;
|
|
264
|
+
const selectDeviceId = (state) => { var _a, _b; return (_b = (_a = state.deviceCredentials.data) === null || _a === void 0 ? void 0 : _a.credentials) === null || _b === void 0 ? void 0 : _b.uuid; };
|
|
265
|
+
/**
|
|
266
|
+
* Reactors
|
|
267
|
+
*/
|
|
268
|
+
const selectShouldFetchDeviceCredentials = createSelector(selectAppWantsToJoin, selectDeviceCredentialsRaw, (wantsToJoin, deviceCredentials) => {
|
|
269
|
+
if (wantsToJoin && !deviceCredentials.isFetching && !deviceCredentials.data) {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
});
|
|
274
|
+
createReactor([selectShouldFetchDeviceCredentials], ({ dispatch }, shouldFetchDeviceCredentials) => {
|
|
275
|
+
if (shouldFetchDeviceCredentials) {
|
|
276
|
+
dispatch(doGetDeviceCredentials());
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const DEFAULT_SOCKET_PATH = "/protocol/socket.io/v4";
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Wrapper class that extends the Socket.IO client library.
|
|
284
|
+
*/
|
|
285
|
+
class ServerSocket {
|
|
286
|
+
constructor(hostName, optionsOverrides) {
|
|
287
|
+
this._socket = io(hostName, {
|
|
288
|
+
path: DEFAULT_SOCKET_PATH,
|
|
289
|
+
randomizationFactor: 0.5,
|
|
290
|
+
reconnectionDelay: 250,
|
|
291
|
+
reconnectionDelayMax: 5000,
|
|
292
|
+
timeout: 5000,
|
|
293
|
+
transports: ["websocket"],
|
|
294
|
+
withCredentials: true,
|
|
295
|
+
...optionsOverrides,
|
|
296
|
+
});
|
|
297
|
+
this._socket.io.on("reconnect", () => {
|
|
298
|
+
this._socket.sendBuffer = [];
|
|
299
|
+
});
|
|
300
|
+
this._socket.io.on("reconnect_attempt", () => {
|
|
301
|
+
if (this._wasConnectedUsingWebsocket) {
|
|
302
|
+
this._socket.io.opts.transports = ["websocket"];
|
|
303
|
+
// only fallback to polling if not safari
|
|
304
|
+
// safari doesn't support cross doamin cookies making load-balancer stickiness not work
|
|
305
|
+
// and if socket.io reconnects to another signal instance with polling it will fail
|
|
306
|
+
// remove if we move signal to a whereby.com subdomain
|
|
307
|
+
if (adapter.browserDetails.browser !== "safari") delete this._wasConnectedUsingWebsocket;
|
|
308
|
+
} else {
|
|
309
|
+
this._socket.io.opts.transports = ["websocket", "polling"];
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
this._socket.on("connect", () => {
|
|
313
|
+
const transport = this.getTransport();
|
|
314
|
+
if (transport === "websocket") {
|
|
315
|
+
this._wasConnectedUsingWebsocket = true;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
connect() {
|
|
321
|
+
if (this.isConnected() || this.isConnecting()) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
this._socket.open();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
disconnect() {
|
|
328
|
+
this._socket.disconnect();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
disconnectOnConnect() {
|
|
332
|
+
this._socket.once("connect", () => {
|
|
333
|
+
this._socket.disconnect();
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
emit() {
|
|
338
|
+
this._socket.emit.apply(this._socket, arguments);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
emitIfConnected(eventName, data) {
|
|
342
|
+
if (!this.isConnected()) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
this.emit(eventName, data);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
getTransport() {
|
|
349
|
+
return (
|
|
350
|
+
this._socket &&
|
|
351
|
+
this._socket.io &&
|
|
352
|
+
this._socket.io.engine &&
|
|
353
|
+
this._socket.io.engine.transport &&
|
|
354
|
+
this._socket.io.engine.transport.name
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
getManager() {
|
|
359
|
+
return this._socket.io;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
isConnecting() {
|
|
363
|
+
return this._socket && this._socket.connecting;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
isConnected() {
|
|
367
|
+
return this._socket && this._socket.connected;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Register a new event handler.
|
|
372
|
+
*
|
|
373
|
+
* @param {string} eventName - Name of the event to listen for.
|
|
374
|
+
* @param {function} handler - The callback function that should be called for the event.
|
|
375
|
+
* @returns {function} Function to deregister the listener.
|
|
376
|
+
*/
|
|
377
|
+
on(eventName, handler) {
|
|
378
|
+
this._socket.on(eventName, handler);
|
|
379
|
+
|
|
380
|
+
return () => {
|
|
381
|
+
this._socket.off(eventName, handler);
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Register a new event handler to be triggered only once.
|
|
387
|
+
*
|
|
388
|
+
* @param {string} eventName - Name of the event to listen for.
|
|
389
|
+
* @param {function} handler - The function that should be called for the event.
|
|
390
|
+
*/
|
|
391
|
+
once(eventName, handler) {
|
|
392
|
+
this._socket.once(eventName, handler);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Deregister an event handler.
|
|
397
|
+
*
|
|
398
|
+
* @param {string} eventName - Name of the event the handler is registered for.
|
|
399
|
+
* @param {function} handler - The callback that will be deregistered.
|
|
400
|
+
*/
|
|
401
|
+
off(eventName, handler) {
|
|
402
|
+
this._socket.off(eventName, handler);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function forwardSocketEvents(socket, dispatch) {
|
|
407
|
+
socket.on("room_joined", (payload) => dispatch(signalEvents.roomJoined(payload)));
|
|
408
|
+
socket.on("new_client", (payload) => dispatch(signalEvents.newClient(payload)));
|
|
409
|
+
socket.on("client_left", (payload) => dispatch(signalEvents.clientLeft(payload)));
|
|
410
|
+
socket.on("audio_enabled", (payload) => dispatch(signalEvents.audioEnabled(payload)));
|
|
411
|
+
socket.on("video_enabled", (payload) => dispatch(signalEvents.videoEnabled(payload)));
|
|
412
|
+
socket.on("client_metadata_received", (payload) => dispatch(signalEvents.clientMetadataReceived(payload)));
|
|
413
|
+
socket.on("chat_message", (payload) => dispatch(signalEvents.chatMessage(payload)));
|
|
414
|
+
socket.on("disconnect", () => dispatch(signalEvents.disconnect()));
|
|
415
|
+
socket.on("room_knocked", (payload) => dispatch(signalEvents.roomKnocked(payload)));
|
|
416
|
+
socket.on("knocker_left", (payload) => dispatch(signalEvents.knockerLeft(payload)));
|
|
417
|
+
socket.on("knock_handled", (payload) => dispatch(signalEvents.knockHandled(payload)));
|
|
418
|
+
socket.on("screenshare_started", (payload) => dispatch(signalEvents.screenshareStarted(payload)));
|
|
419
|
+
socket.on("screenshare_stopped", (payload) => dispatch(signalEvents.screenshareStopped(payload)));
|
|
420
|
+
socket.on("cloud_recording_started", (payload) => dispatch(signalEvents.cloudRecordingStarted(payload)));
|
|
421
|
+
socket.on("cloud_recording_stopped", () => dispatch(signalEvents.cloudRecordingStopped()));
|
|
422
|
+
socket.on("streaming_stopped", () => dispatch(signalEvents.streamingStopped()));
|
|
423
|
+
}
|
|
424
|
+
const SIGNAL_BASE_URL = "wss://signal.appearin.net" ;
|
|
425
|
+
function createSocket() {
|
|
426
|
+
const parsedUrl = new URL(SIGNAL_BASE_URL);
|
|
427
|
+
const socketHost = parsedUrl.origin;
|
|
428
|
+
const socketOverrides = {
|
|
429
|
+
autoConnect: false,
|
|
430
|
+
};
|
|
431
|
+
return new ServerSocket(socketHost, socketOverrides);
|
|
432
|
+
}
|
|
433
|
+
const initialState$e = {
|
|
434
|
+
deviceIdentified: false,
|
|
435
|
+
isIdentifyingDevice: false,
|
|
436
|
+
status: "",
|
|
437
|
+
socket: null,
|
|
438
|
+
};
|
|
439
|
+
const signalConnectionSlice = createSlice({
|
|
440
|
+
name: "signalConnection",
|
|
441
|
+
initialState: initialState$e,
|
|
442
|
+
reducers: {
|
|
443
|
+
socketConnecting: (state) => {
|
|
444
|
+
return Object.assign(Object.assign({}, state), { status: "connecting" });
|
|
445
|
+
},
|
|
446
|
+
socketConnected: (state, action) => {
|
|
447
|
+
return Object.assign(Object.assign({}, state), { socket: action.payload, status: "connected" });
|
|
448
|
+
},
|
|
449
|
+
socketDisconnected: (state) => {
|
|
450
|
+
return Object.assign(Object.assign({}, state), { status: "disconnected" });
|
|
451
|
+
},
|
|
452
|
+
socketReconnecting: (state) => {
|
|
453
|
+
return Object.assign(Object.assign({}, state), { status: "reconnect" });
|
|
454
|
+
},
|
|
455
|
+
deviceIdentifying: (state) => {
|
|
456
|
+
return Object.assign(Object.assign({}, state), { isIdentifyingDevice: true });
|
|
457
|
+
},
|
|
458
|
+
deviceIdentified: (state) => {
|
|
459
|
+
return Object.assign(Object.assign({}, state), { deviceIdentified: true, isIdentifyingDevice: false });
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
const { deviceIdentifying, deviceIdentified, socketConnected, socketConnecting, socketDisconnected } = signalConnectionSlice.actions;
|
|
464
|
+
/**
|
|
465
|
+
* Action creators
|
|
466
|
+
*/
|
|
467
|
+
const doSignalSocketConnect = createAppThunk(() => {
|
|
468
|
+
return (dispatch, getState) => {
|
|
469
|
+
if (selectSignalConnectionSocket(getState())) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
dispatch(socketConnecting());
|
|
473
|
+
const socket = createSocket();
|
|
474
|
+
socket.on("connect", () => dispatch(socketConnected(socket)));
|
|
475
|
+
socket.on("device_identified", () => dispatch(deviceIdentified()));
|
|
476
|
+
socket.getManager().on("reconnect", () => dispatch(doSignalReconnect()));
|
|
477
|
+
forwardSocketEvents(socket, dispatch);
|
|
478
|
+
socket.connect();
|
|
479
|
+
};
|
|
480
|
+
});
|
|
481
|
+
const doSignalIdentifyDevice = createAppThunk(({ deviceCredentials }) => (dispatch, getState) => {
|
|
482
|
+
const state = getState();
|
|
483
|
+
const signalSocket = selectSignalConnectionSocket(state);
|
|
484
|
+
if (!signalSocket) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
signalSocket.emit("identify_device", { deviceCredentials });
|
|
488
|
+
dispatch(deviceIdentifying());
|
|
489
|
+
});
|
|
490
|
+
const doSignalDisconnect = createAppThunk(() => (dispatch, getState) => {
|
|
491
|
+
const socket = selectSignalConnectionRaw(getState()).socket;
|
|
492
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("leave_room");
|
|
493
|
+
socket === null || socket === void 0 ? void 0 : socket.disconnect();
|
|
494
|
+
dispatch(socketDisconnected());
|
|
495
|
+
});
|
|
496
|
+
const doSignalReconnect = createAppThunk(() => (dispatch, getState) => {
|
|
497
|
+
const deviceCredentialsRaw = selectDeviceCredentialsRaw(getState());
|
|
498
|
+
dispatch(socketReconnecting());
|
|
499
|
+
if (deviceCredentialsRaw.data) {
|
|
500
|
+
dispatch(doSignalIdentifyDevice({ deviceCredentials: deviceCredentialsRaw.data }));
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
const { socketReconnecting } = signalConnectionSlice.actions;
|
|
504
|
+
/**
|
|
505
|
+
* Selectors
|
|
506
|
+
*/
|
|
507
|
+
const selectSignalConnectionRaw = (state) => state.signalConnection;
|
|
508
|
+
const selectSignalIsIdentifyingDevice = (state) => state.signalConnection.isIdentifyingDevice;
|
|
509
|
+
const selectSignalConnectionDeviceIdentified = (state) => state.signalConnection.deviceIdentified;
|
|
510
|
+
const selectSignalStatus = (state) => state.signalConnection.status;
|
|
511
|
+
const selectSignalConnectionSocket = (state) => state.signalConnection.socket;
|
|
512
|
+
/**
|
|
513
|
+
* Reactors
|
|
514
|
+
*/
|
|
515
|
+
startAppListening({
|
|
516
|
+
actionCreator: appLeft,
|
|
517
|
+
effect: (_, { dispatch }) => {
|
|
518
|
+
dispatch(doSignalDisconnect());
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
const selectShouldConnectSignal = createSelector(selectAppWantsToJoin, selectSignalStatus, (wantsToJoin, signalStatus) => {
|
|
522
|
+
if (wantsToJoin && ["", "reconnect"].includes(signalStatus)) {
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
return false;
|
|
526
|
+
});
|
|
527
|
+
createReactor([selectShouldConnectSignal], ({ dispatch }, shouldConnectSignal) => {
|
|
528
|
+
if (shouldConnectSignal) {
|
|
529
|
+
dispatch(doSignalSocketConnect());
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
const selectShouldIdentifyDevice = createSelector(selectDeviceCredentialsRaw, selectSignalStatus, selectSignalConnectionDeviceIdentified, selectSignalIsIdentifyingDevice, (deviceCredentialsRaw, signalStatus, deviceIdentified, isIdentifyingDevice) => {
|
|
533
|
+
if (deviceCredentialsRaw.data && signalStatus === "connected" && !deviceIdentified && !isIdentifyingDevice) {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
return false;
|
|
537
|
+
});
|
|
538
|
+
createReactor([selectShouldIdentifyDevice, selectDeviceCredentialsRaw], ({ dispatch }, shouldIdentifyDevice, deviceCredentialsRaw) => {
|
|
539
|
+
if (shouldIdentifyDevice && deviceCredentialsRaw.data) {
|
|
540
|
+
dispatch(doSignalIdentifyDevice({ deviceCredentials: deviceCredentialsRaw.data }));
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const initialState$d = {
|
|
545
|
+
chatMessages: [],
|
|
546
|
+
};
|
|
547
|
+
const chatSlice = createSlice({
|
|
548
|
+
name: "chat",
|
|
549
|
+
initialState: initialState$d,
|
|
550
|
+
reducers: {},
|
|
551
|
+
extraReducers(builder) {
|
|
552
|
+
builder.addCase(signalEvents.chatMessage, (state, action) => {
|
|
553
|
+
const message = {
|
|
554
|
+
senderId: action.payload.senderId,
|
|
555
|
+
timestamp: action.payload.timestamp,
|
|
556
|
+
text: action.payload.text,
|
|
557
|
+
};
|
|
558
|
+
return Object.assign(Object.assign({}, state), { chatMessages: [...state.chatMessages, message] });
|
|
559
|
+
});
|
|
560
|
+
},
|
|
561
|
+
});
|
|
562
|
+
/**
|
|
563
|
+
* Action creators
|
|
564
|
+
*/
|
|
565
|
+
const doSendChatMessage = createAppThunk((payload) => (_, getState) => {
|
|
566
|
+
const state = getState();
|
|
567
|
+
const socket = selectSignalConnectionRaw(state).socket;
|
|
568
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("chat_message", { text: payload.text });
|
|
569
|
+
});
|
|
570
|
+
const selectChatMessages = (state) => state.chat.chatMessages;
|
|
571
|
+
|
|
572
|
+
const initialState$c = {
|
|
573
|
+
isRecording: false,
|
|
574
|
+
error: null,
|
|
575
|
+
startedAt: undefined,
|
|
576
|
+
};
|
|
577
|
+
const cloudRecordingSlice = createSlice({
|
|
578
|
+
name: "cloudRecording",
|
|
579
|
+
initialState: initialState$c,
|
|
580
|
+
reducers: {
|
|
581
|
+
recordingRequestStarted: (state) => {
|
|
582
|
+
return Object.assign(Object.assign({}, state), { status: "requested" });
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
extraReducers: (builder) => {
|
|
586
|
+
builder.addCase(signalEvents.cloudRecordingStopped, (state) => {
|
|
587
|
+
return Object.assign(Object.assign({}, state), { isRecording: false, status: undefined });
|
|
588
|
+
});
|
|
589
|
+
builder.addCase(signalEvents.cloudRecordingStarted, (state, action) => {
|
|
590
|
+
const { payload } = action;
|
|
591
|
+
if (!payload.error) {
|
|
592
|
+
return state;
|
|
593
|
+
}
|
|
594
|
+
return Object.assign(Object.assign({}, state), { isRecording: false, status: "error", error: payload.error });
|
|
595
|
+
});
|
|
596
|
+
builder.addCase(signalEvents.newClient, (state, { payload }) => {
|
|
597
|
+
var _a;
|
|
598
|
+
const { client } = payload;
|
|
599
|
+
if (((_a = client.role) === null || _a === void 0 ? void 0 : _a.roleName) === "recorder") {
|
|
600
|
+
return Object.assign(Object.assign({}, state), { isRecording: true, status: "recording", startedAt: client.startedCloudRecordingAt
|
|
601
|
+
? new Date(client.startedCloudRecordingAt).getTime()
|
|
602
|
+
: new Date().getTime() });
|
|
603
|
+
}
|
|
604
|
+
return state;
|
|
605
|
+
});
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
/**
|
|
609
|
+
* Action creators
|
|
610
|
+
*/
|
|
611
|
+
const { recordingRequestStarted } = cloudRecordingSlice.actions;
|
|
612
|
+
const doStartCloudRecording = createAppThunk(() => (dispatch, getState) => {
|
|
613
|
+
const state = getState();
|
|
614
|
+
const socket = selectSignalConnectionRaw(state).socket;
|
|
615
|
+
const status = selectCloudRecordingStatus(state);
|
|
616
|
+
if (status && ["recording", "requested"].includes(status)) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("start_recording", {
|
|
620
|
+
recording: "cloud",
|
|
621
|
+
});
|
|
622
|
+
dispatch(recordingRequestStarted());
|
|
623
|
+
});
|
|
624
|
+
const doStopCloudRecording = createAppThunk(() => (dispatch, getState) => {
|
|
625
|
+
const state = getState();
|
|
626
|
+
const socket = selectSignalConnectionRaw(state).socket;
|
|
627
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("stop_recording");
|
|
628
|
+
});
|
|
629
|
+
/**
|
|
630
|
+
* Selectors
|
|
631
|
+
*/
|
|
632
|
+
const selectCloudRecordingRaw = (state) => state.cloudRecording;
|
|
633
|
+
const selectCloudRecordingStatus = (state) => state.cloudRecording.status;
|
|
634
|
+
|
|
122
635
|
const isSafari = adapter.browserDetails.browser === "safari";
|
|
123
636
|
|
|
124
637
|
// Expects format 640x360@25, returns [width, height, fps]
|
|
@@ -318,6 +831,63 @@ function getUserMedia(constraints) {
|
|
|
318
831
|
});
|
|
319
832
|
}
|
|
320
833
|
|
|
834
|
+
function getSettingsFromTrack(kind, track, devices, lastUsedId) {
|
|
835
|
+
let settings = { deviceId: null };
|
|
836
|
+
|
|
837
|
+
if (!track) {
|
|
838
|
+
// In SFU V2 the track can be closed by the RtcManager, so check if the
|
|
839
|
+
// last used deviceId still is available
|
|
840
|
+
if (lastUsedId && devices) {
|
|
841
|
+
settings.deviceId = devices.find((d) => d.deviceId === lastUsedId && d.kind === kind)?.deviceId;
|
|
842
|
+
}
|
|
843
|
+
return settings;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
settings.label = track.label;
|
|
847
|
+
|
|
848
|
+
// if MediaTrackSettings.deviceId is supported (not firefox android/esr)
|
|
849
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings#Browser_compatibility
|
|
850
|
+
if (track.getSettings) {
|
|
851
|
+
settings = { ...settings, ...track.getSettings() };
|
|
852
|
+
}
|
|
853
|
+
if (settings.deviceId) return settings;
|
|
854
|
+
// if MediaTrackCapabilities is supported (not by firefox or samsung internet in general)
|
|
855
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/getCapabilities#Browser_compatibility
|
|
856
|
+
if (track.getCapabilities) {
|
|
857
|
+
settings.deviceId = track.getCapabilities().deviceId;
|
|
858
|
+
}
|
|
859
|
+
if (settings.deviceId) return settings;
|
|
860
|
+
|
|
861
|
+
// Firefox ESR (guessing), has no way of getting deviceId, but
|
|
862
|
+
// it probably gives us label, let's use that to find it!
|
|
863
|
+
if (track.label && devices) {
|
|
864
|
+
settings.deviceId = devices.find((d) => track.label === d.label && d.kind === kind)?.deviceId;
|
|
865
|
+
}
|
|
866
|
+
if (settings.deviceId) return settings;
|
|
867
|
+
|
|
868
|
+
// Okay. As if the above wasn't hacky enough (it was), this
|
|
869
|
+
// is even more, basically see what we sent before
|
|
870
|
+
// It's really sad if we get down to this point.
|
|
871
|
+
settings.deviceId = track.getConstraints()?.deviceId?.exact;
|
|
872
|
+
settings.broken = 1; // just a hint
|
|
873
|
+
return settings;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Gets audio and video device data from stream
|
|
878
|
+
*
|
|
879
|
+
* @returns {{video: {deviceId}, audio: {deviceId}}} - the ids are null if not found
|
|
880
|
+
*/
|
|
881
|
+
function getDeviceData({ audioTrack, videoTrack, devices, stoppedVideoTrack, lastAudioId, lastVideoId }) {
|
|
882
|
+
const usable = (d) => (d?.readyState === "live" ? d : null);
|
|
883
|
+
videoTrack = usable(videoTrack) || stoppedVideoTrack;
|
|
884
|
+
audioTrack = usable(audioTrack);
|
|
885
|
+
const video = getSettingsFromTrack("videoinput", videoTrack, devices, lastVideoId);
|
|
886
|
+
const audio = getSettingsFromTrack("audioinput", audioTrack, devices, lastAudioId);
|
|
887
|
+
|
|
888
|
+
return { video, audio };
|
|
889
|
+
}
|
|
890
|
+
|
|
321
891
|
/**
|
|
322
892
|
* Stops all tracks in a media stream.
|
|
323
893
|
*/
|
|
@@ -496,374 +1066,1152 @@ async function getStream(constraintOpt, { replaceStream, fallback = true } = {})
|
|
|
496
1066
|
return { error: error && addDetails(error), stream, replacedTracks };
|
|
497
1067
|
}
|
|
498
1068
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
1069
|
+
function compareLocalDevices(before, after) {
|
|
1070
|
+
const [beforeByKind, afterByKind] = [before, after].map((list) =>
|
|
1071
|
+
list
|
|
1072
|
+
.filter((device) => device.kind && device.deviceId)
|
|
1073
|
+
.reduce(
|
|
1074
|
+
(result, device) => ({
|
|
1075
|
+
...result,
|
|
1076
|
+
[device.kind]: { ...result[device.kind], [device.deviceId]: device },
|
|
1077
|
+
}),
|
|
1078
|
+
{}
|
|
1079
|
+
)
|
|
1080
|
+
);
|
|
1081
|
+
const changesByKind = {};
|
|
1082
|
+
// find devices removed
|
|
1083
|
+
before.forEach((device) => {
|
|
1084
|
+
if (!device.kind || !device.deviceId) return;
|
|
1085
|
+
if (!changesByKind[device.kind]) changesByKind[device.kind] = { added: {}, removed: {}, changed: {} };
|
|
1086
|
+
if (!afterByKind[device.kind] || !afterByKind[device.kind][device.deviceId]) {
|
|
1087
|
+
changesByKind[device.kind].removed[device.deviceId] = device;
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
// find devices either added or changed
|
|
1091
|
+
after.forEach((device) => {
|
|
1092
|
+
if (!device.kind || !device.deviceId) return;
|
|
1093
|
+
if (!changesByKind[device.kind]) changesByKind[device.kind] = { added: {}, removed: {}, changed: {} };
|
|
1094
|
+
if (!beforeByKind[device.kind] || !beforeByKind[device.kind][device.deviceId]) {
|
|
1095
|
+
changesByKind[device.kind].added[device.deviceId] = device;
|
|
1096
|
+
} else if (
|
|
1097
|
+
beforeByKind[device.kind][device.deviceId].label && // ignore when initially without label
|
|
1098
|
+
beforeByKind[device.kind][device.deviceId].label !== device.label
|
|
1099
|
+
) {
|
|
1100
|
+
changesByKind[device.kind].changed[device.deviceId] = device;
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
return changesByKind;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
function getUpdatedDevices({ oldDevices, newDevices, currentAudioId, currentVideoId, currentSpeakerId }) {
|
|
1107
|
+
const changesByKind = compareLocalDevices(oldDevices, newDevices);
|
|
1108
|
+
const changedDevices = {};
|
|
1109
|
+
const addedDevices = {};
|
|
1110
|
+
[
|
|
1111
|
+
["audioinput", currentAudioId],
|
|
1112
|
+
["videoinput", currentVideoId],
|
|
1113
|
+
["audiooutput", currentSpeakerId],
|
|
1114
|
+
].forEach(([kind, currentDeviceId]) => {
|
|
1115
|
+
const changes = changesByKind[kind];
|
|
1116
|
+
if (!changes) {
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
if (currentDeviceId) {
|
|
1120
|
+
// fall back to default if removed
|
|
1121
|
+
if (changes.removed[currentDeviceId]) {
|
|
1122
|
+
changedDevices[kind] = { deviceId: null }; // let browser decide
|
|
1123
|
+
if (kind === "audiooutput") {
|
|
1124
|
+
const fallbackSpeakerDevice = newDevices.find((d) => d.kind === "audiooutput");
|
|
1125
|
+
changedDevices[kind] = { deviceId: fallbackSpeakerDevice?.deviceId };
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
// re-request if device has changed
|
|
1129
|
+
if (changes.changed[currentDeviceId]) {
|
|
1130
|
+
changedDevices[kind] = { deviceId: currentDeviceId };
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
// request new device if added
|
|
1134
|
+
if (Object.keys(changes.added).length) {
|
|
1135
|
+
const [deviceAdded] = Object.keys(changes.added).slice(0, 1);
|
|
1136
|
+
const add = changes.added[deviceAdded];
|
|
1137
|
+
// device props are not enumerable (used in notificatio
|
|
1138
|
+
addedDevices[kind] = { deviceId: add.deviceId, label: add.label, kind: add.kind };
|
|
1139
|
+
}
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
return { addedDevices, changedDevices };
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
const initialState$b = {
|
|
1146
|
+
busyDeviceIds: [],
|
|
1147
|
+
cameraEnabled: false,
|
|
1148
|
+
devices: [],
|
|
1149
|
+
isSettingCameraDevice: false,
|
|
1150
|
+
isSettingMicrophoneDevice: false,
|
|
1151
|
+
isTogglingCamera: false,
|
|
1152
|
+
microphoneEnabled: false,
|
|
1153
|
+
status: "",
|
|
1154
|
+
isSwitchingStream: false,
|
|
1155
|
+
};
|
|
1156
|
+
const localMediaSlice = createSlice({
|
|
1157
|
+
name: "localMedia",
|
|
1158
|
+
initialState: initialState$b,
|
|
1159
|
+
reducers: {
|
|
1160
|
+
deviceBusy(state, action) {
|
|
1161
|
+
if (state.busyDeviceIds.includes(action.payload.deviceId)) {
|
|
1162
|
+
return state;
|
|
552
1163
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
1164
|
+
return Object.assign(Object.assign({}, state), { busyDeviceIds: [...state.busyDeviceIds, action.payload.deviceId] });
|
|
1165
|
+
},
|
|
1166
|
+
toggleCameraEnabled(state, action) {
|
|
1167
|
+
var _a;
|
|
1168
|
+
return Object.assign(Object.assign({}, state), { cameraEnabled: (_a = action.payload.enabled) !== null && _a !== void 0 ? _a : !state.cameraEnabled });
|
|
1169
|
+
},
|
|
1170
|
+
setCurrentCameraDeviceId(state, action) {
|
|
1171
|
+
return Object.assign(Object.assign({}, state), { currentCameraDeviceId: action.payload.deviceId });
|
|
1172
|
+
},
|
|
1173
|
+
toggleMicrophoneEnabled(state, action) {
|
|
1174
|
+
var _a;
|
|
1175
|
+
return Object.assign(Object.assign({}, state), { microphoneEnabled: (_a = action.payload.enabled) !== null && _a !== void 0 ? _a : !state.microphoneEnabled });
|
|
1176
|
+
},
|
|
1177
|
+
setCurrentMicrophoneDeviceId(state, action) {
|
|
1178
|
+
return Object.assign(Object.assign({}, state), { currentMicrophoneDeviceId: action.payload.deviceId });
|
|
1179
|
+
},
|
|
1180
|
+
setDevices(state, action) {
|
|
1181
|
+
return Object.assign(Object.assign({}, state), { devices: action.payload.devices });
|
|
1182
|
+
},
|
|
1183
|
+
setLocalMediaStream(state, action) {
|
|
1184
|
+
return Object.assign(Object.assign({}, state), { stream: action.payload.stream });
|
|
1185
|
+
},
|
|
1186
|
+
setLocalMediaOptions(state, action) {
|
|
1187
|
+
return Object.assign(Object.assign({}, state), { options: action.payload.options });
|
|
1188
|
+
},
|
|
1189
|
+
localMediaStopped(state) {
|
|
1190
|
+
return Object.assign(Object.assign({}, state), { status: "stopped", stream: undefined });
|
|
1191
|
+
},
|
|
1192
|
+
localStreamMetadataUpdated(state, action) {
|
|
1193
|
+
const { audio, video } = action.payload;
|
|
1194
|
+
return Object.assign(Object.assign({}, state), { currentCameraDeviceId: video.deviceId, currentMicrophoneDeviceId: audio.deviceId, busyDeviceIds: state.busyDeviceIds.filter((id) => id !== audio.deviceId && id !== video.deviceId) });
|
|
1195
|
+
},
|
|
1196
|
+
},
|
|
1197
|
+
extraReducers: (builder) => {
|
|
1198
|
+
builder.addCase(doAppJoin, (state, action) => {
|
|
1199
|
+
return Object.assign(Object.assign({}, state), { options: action.payload.localMediaOptions });
|
|
1200
|
+
});
|
|
1201
|
+
builder.addCase(doSetDevice.pending, (state, action) => {
|
|
1202
|
+
const { audio, video } = action.meta.arg;
|
|
1203
|
+
return Object.assign(Object.assign({}, state), { isSettingCameraDevice: video, isSettingMicrophoneDevice: audio });
|
|
1204
|
+
});
|
|
1205
|
+
builder.addCase(doSetDevice.fulfilled, (state, action) => {
|
|
1206
|
+
const { audio, video } = action.meta.arg;
|
|
1207
|
+
return Object.assign(Object.assign({}, state), { isSettingCameraDevice: video ? false : state.isSettingCameraDevice, isSettingMicrophoneDevice: audio ? false : state.isSettingMicrophoneDevice });
|
|
1208
|
+
});
|
|
1209
|
+
builder.addCase(doSetDevice.rejected, (state, action) => {
|
|
1210
|
+
const { audio, video } = action.meta.arg;
|
|
1211
|
+
return Object.assign(Object.assign({}, state), { isSettingCameraDevice: video ? false : state.isSettingCameraDevice, isSettingMicrophoneDevice: audio ? false : state.isSettingMicrophoneDevice, cameraDeviceError: video ? action.error : state.cameraDeviceError, microphoneDeviceError: audio ? action.error : state.microphoneDeviceError });
|
|
1212
|
+
});
|
|
1213
|
+
builder.addCase(doToggleCamera.pending, (state) => {
|
|
1214
|
+
return Object.assign(Object.assign({}, state), { isTogglingCamera: true });
|
|
1215
|
+
});
|
|
1216
|
+
builder.addCase(doToggleCamera.fulfilled, (state) => {
|
|
1217
|
+
return Object.assign(Object.assign({}, state), { isTogglingCamera: false });
|
|
1218
|
+
});
|
|
1219
|
+
builder.addCase(doUpdateDeviceList.fulfilled, (state, action) => {
|
|
1220
|
+
return Object.assign(Object.assign({}, state), { devices: action.payload.devices });
|
|
1221
|
+
});
|
|
1222
|
+
builder.addCase(doStartLocalMedia.pending, (state) => {
|
|
1223
|
+
return Object.assign(Object.assign({}, state), { status: "starting" });
|
|
1224
|
+
});
|
|
1225
|
+
builder.addCase(doStartLocalMedia.fulfilled, (state, { payload: { stream, onDeviceChange } }) => {
|
|
1226
|
+
let cameraDeviceId = undefined;
|
|
1227
|
+
let cameraEnabled = false;
|
|
1228
|
+
let microphoneDeviceId = undefined;
|
|
1229
|
+
let microphoneEnabled = false;
|
|
1230
|
+
const audioTrack = stream.getAudioTracks()[0];
|
|
1231
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
1232
|
+
if (audioTrack) {
|
|
1233
|
+
microphoneDeviceId = audioTrack.getSettings().deviceId;
|
|
1234
|
+
microphoneEnabled = audioTrack.enabled;
|
|
582
1235
|
}
|
|
583
|
-
|
|
584
|
-
|
|
1236
|
+
if (videoTrack) {
|
|
1237
|
+
cameraEnabled = videoTrack.enabled;
|
|
1238
|
+
cameraDeviceId = videoTrack.getSettings().deviceId;
|
|
585
1239
|
}
|
|
586
|
-
|
|
1240
|
+
return Object.assign(Object.assign({}, state), { stream, status: "started", currentCameraDeviceId: cameraDeviceId, currentMicrophoneDeviceId: microphoneDeviceId, cameraEnabled,
|
|
1241
|
+
microphoneEnabled,
|
|
1242
|
+
onDeviceChange });
|
|
587
1243
|
});
|
|
1244
|
+
builder.addCase(doStartLocalMedia.rejected, (state, action) => {
|
|
1245
|
+
return Object.assign(Object.assign({}, state), { status: "error", startError: action.error });
|
|
1246
|
+
});
|
|
1247
|
+
builder.addCase(doSwitchLocalStream.pending, (state) => {
|
|
1248
|
+
return Object.assign(Object.assign({}, state), { isSwitchingStream: true });
|
|
1249
|
+
});
|
|
1250
|
+
builder.addCase(doSwitchLocalStream.fulfilled, (state) => {
|
|
1251
|
+
return Object.assign(Object.assign({}, state), { isSwitchingStream: false });
|
|
1252
|
+
});
|
|
1253
|
+
builder.addCase(doSwitchLocalStream.rejected, (state) => {
|
|
1254
|
+
return Object.assign(Object.assign({}, state), { isSwitchingStream: false });
|
|
1255
|
+
});
|
|
1256
|
+
},
|
|
1257
|
+
});
|
|
1258
|
+
/**
|
|
1259
|
+
* Action creators
|
|
1260
|
+
*/
|
|
1261
|
+
const { deviceBusy, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId, toggleCameraEnabled, toggleMicrophoneEnabled, setLocalMediaOptions, setLocalMediaStream, localMediaStopped, localStreamMetadataUpdated, } = localMediaSlice.actions;
|
|
1262
|
+
const doToggleCamera = createAppAsyncThunk("localMedia/doToggleCamera", (_, { getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1263
|
+
const state = getState();
|
|
1264
|
+
const stream = selectLocalMediaStream(state);
|
|
1265
|
+
if (!stream) {
|
|
1266
|
+
return;
|
|
588
1267
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
1268
|
+
let track = stream.getVideoTracks()[0];
|
|
1269
|
+
const enabled = selectIsCameraEnabled(state);
|
|
1270
|
+
// Only stop tracks if we fully own the media stream
|
|
1271
|
+
const shouldStopTrack = selectLocalMediaOwnsStream(state);
|
|
1272
|
+
try {
|
|
1273
|
+
if (enabled) {
|
|
1274
|
+
if (track) {
|
|
1275
|
+
// We have existing video track, just enable it
|
|
1276
|
+
track.enabled = true;
|
|
1277
|
+
}
|
|
1278
|
+
else {
|
|
1279
|
+
// We dont have video track, get new one
|
|
1280
|
+
const constraintsOptions = selectLocalMediaConstraintsOptions(state);
|
|
1281
|
+
const cameraDeviceId = selectCurrentCameraDeviceId(state);
|
|
1282
|
+
yield getStream(Object.assign(Object.assign({}, constraintsOptions), { audioId: false, videoId: cameraDeviceId, type: "exact" }), { replaceStream: stream });
|
|
1283
|
+
track = stream.getVideoTracks()[0];
|
|
1284
|
+
}
|
|
593
1285
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
audioTrack.enabled = this._microphoneEnabled;
|
|
598
|
-
}
|
|
599
|
-
startScreenshare() {
|
|
600
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
601
|
-
if (this.screenshareStream) {
|
|
602
|
-
return this.screenshareStream;
|
|
1286
|
+
else {
|
|
1287
|
+
if (!track) {
|
|
1288
|
+
return;
|
|
603
1289
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
609
|
-
stopScreenshare() {
|
|
610
|
-
var _a;
|
|
611
|
-
(_a = this.screenshareStream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((track) => track.stop());
|
|
612
|
-
this.screenshareStream = undefined;
|
|
613
|
-
}
|
|
614
|
-
_getConstraintsOptions() {
|
|
615
|
-
return {
|
|
616
|
-
devices: this._devices,
|
|
617
|
-
options: {
|
|
618
|
-
disableAEC: false,
|
|
619
|
-
disableAGC: false,
|
|
620
|
-
hd: true,
|
|
621
|
-
lax: false,
|
|
622
|
-
lowDataMode: false,
|
|
623
|
-
simulcast: true,
|
|
624
|
-
widescreen: true,
|
|
625
|
-
},
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
_setDevice({ audioId, videoId }) {
|
|
629
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
630
|
-
const { replacedTracks } = yield getStream(Object.assign(Object.assign({}, this._getConstraintsOptions()), { audioId,
|
|
631
|
-
videoId, type: "exact" }), { replaceStream: this.stream });
|
|
632
|
-
if (replacedTracks) {
|
|
633
|
-
replacedTracks.forEach((oldTrack) => {
|
|
634
|
-
const newTrack = oldTrack.kind === "audio" ? this.stream.getAudioTracks()[0] : this.stream.getVideoTracks()[0];
|
|
635
|
-
this._rtcManagers.forEach((rtcManager) => {
|
|
636
|
-
rtcManager.replaceTrack(oldTrack, newTrack);
|
|
637
|
-
});
|
|
638
|
-
});
|
|
1290
|
+
track.enabled = false;
|
|
1291
|
+
if (shouldStopTrack) {
|
|
1292
|
+
track.stop();
|
|
1293
|
+
stream.removeTrack(track);
|
|
639
1294
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
1295
|
+
}
|
|
1296
|
+
// Dispatch event on stream to allow RTC layer effects
|
|
1297
|
+
stream.dispatchEvent(new CustomEvent("stopresumevideo", { detail: { track, enable: enabled } }));
|
|
1298
|
+
}
|
|
1299
|
+
catch (error) {
|
|
1300
|
+
return rejectWithValue(error);
|
|
1301
|
+
}
|
|
1302
|
+
}));
|
|
1303
|
+
const doToggleMicrophone = createAppAsyncThunk("localMedia/doToggleMicrophone", (_, { getState }) => {
|
|
1304
|
+
var _a;
|
|
1305
|
+
const state = getState();
|
|
1306
|
+
const stream = selectLocalMediaStream(state);
|
|
1307
|
+
if (!stream) {
|
|
1308
|
+
return;
|
|
644
1309
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
});
|
|
1310
|
+
const enabled = selectIsMicrophoneEnabled(state);
|
|
1311
|
+
const audioTrack = (_a = stream.getAudioTracks()) === null || _a === void 0 ? void 0 : _a[0];
|
|
1312
|
+
if (!audioTrack) {
|
|
1313
|
+
return;
|
|
650
1314
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
1315
|
+
audioTrack.enabled = enabled;
|
|
1316
|
+
});
|
|
1317
|
+
const doSetDevice = createAppAsyncThunk("localMedia/reactSetDevice", ({ audio, video }, { getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1318
|
+
try {
|
|
1319
|
+
const state = getState();
|
|
1320
|
+
const stream = selectLocalMediaStream(state);
|
|
1321
|
+
if (!stream) {
|
|
1322
|
+
throw new Error("No stream");
|
|
1323
|
+
}
|
|
1324
|
+
const audioId = audio ? selectCurrentMicrophoneDeviceId(state) : false;
|
|
1325
|
+
const videoId = video ? selectCurrentCameraDeviceId(state) : false;
|
|
1326
|
+
const constraintsOptions = selectLocalMediaConstraintsOptions(state);
|
|
1327
|
+
const { replacedTracks } = yield getStream(Object.assign(Object.assign({}, constraintsOptions), { audioId,
|
|
1328
|
+
videoId, type: "exact" }), { replaceStream: stream });
|
|
1329
|
+
const isAudioEnabled = selectIsMicrophoneEnabled(state);
|
|
1330
|
+
stream.getAudioTracks().forEach((track) => (track.enabled = isAudioEnabled));
|
|
1331
|
+
const isVideoEnabled = selectIsCameraEnabled(state);
|
|
1332
|
+
stream.getVideoTracks().forEach((track) => (track.enabled = isVideoEnabled));
|
|
1333
|
+
return { replacedTracks };
|
|
1334
|
+
}
|
|
1335
|
+
catch (error) {
|
|
1336
|
+
return rejectWithValue(error);
|
|
1337
|
+
}
|
|
1338
|
+
}));
|
|
1339
|
+
const doUpdateDeviceList = createAppAsyncThunk("localMedia/doUpdateDeviceList", (_, { getState, dispatch, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1340
|
+
var _a, _b;
|
|
1341
|
+
const state = getState();
|
|
1342
|
+
let newDevices = [];
|
|
1343
|
+
let oldDevices = [];
|
|
1344
|
+
const stream = selectLocalMediaStream(state);
|
|
1345
|
+
const busy = selectBusyDeviceIds(state);
|
|
1346
|
+
try {
|
|
1347
|
+
newDevices = yield navigator.mediaDevices.enumerateDevices();
|
|
1348
|
+
oldDevices = selectLocalMediaDevices(state);
|
|
1349
|
+
const shouldHandleDeviceUpdate = stream &&
|
|
1350
|
+
!selectLocalMediaIsSwitchingStream(state) &&
|
|
1351
|
+
newDevices &&
|
|
1352
|
+
oldDevices &&
|
|
1353
|
+
oldDevices.find((d) => d.deviceId);
|
|
1354
|
+
if (!shouldHandleDeviceUpdate) {
|
|
1355
|
+
return { devices: newDevices };
|
|
1356
|
+
}
|
|
1357
|
+
const { changedDevices } = getUpdatedDevices({
|
|
1358
|
+
oldDevices,
|
|
1359
|
+
newDevices,
|
|
1360
|
+
currentAudioId: selectCurrentMicrophoneDeviceId(state),
|
|
1361
|
+
currentVideoId: selectCurrentCameraDeviceId(state),
|
|
655
1362
|
});
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1363
|
+
let autoSwitchAudioId = (_a = changedDevices.audioinput) === null || _a === void 0 ? void 0 : _a.deviceId;
|
|
1364
|
+
let autoSwitchVideoId = (_b = changedDevices.videoinput) === null || _b === void 0 ? void 0 : _b.deviceId;
|
|
1365
|
+
// eslint-disable-next-line no-inner-declarations
|
|
1366
|
+
function nextId(devices, id) {
|
|
1367
|
+
const curIdx = id ? devices.findIndex((d) => d.deviceId === id) : 0;
|
|
1368
|
+
return (devices[(curIdx + 1) % devices.length] || {}).deviceId;
|
|
1369
|
+
}
|
|
1370
|
+
if (autoSwitchVideoId !== undefined) {
|
|
1371
|
+
const videoDevices = selectLocalMediaDevices(state).filter((d) => d.kind === "videoinput");
|
|
1372
|
+
const videoId = selectCurrentCameraDeviceId(state);
|
|
1373
|
+
let nextVideoId = nextId(videoDevices.filter((d) => !busy.includes(d.deviceId)), videoId);
|
|
1374
|
+
if (!nextVideoId || videoId === nextVideoId) {
|
|
1375
|
+
nextVideoId = nextId(videoDevices, videoId);
|
|
669
1376
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
detail: {
|
|
673
|
-
error,
|
|
674
|
-
},
|
|
675
|
-
}));
|
|
676
|
-
throw error;
|
|
1377
|
+
if (videoId !== nextVideoId) {
|
|
1378
|
+
autoSwitchVideoId = nextVideoId;
|
|
677
1379
|
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
if (
|
|
684
|
-
|
|
685
|
-
const cameraTrack = this.stream.getVideoTracks()[0];
|
|
686
|
-
if (cameraTrack) {
|
|
687
|
-
this._cameraEnabled = cameraTrack.enabled;
|
|
688
|
-
this._currentCameraDeviceId = cameraTrack.getSettings().deviceId;
|
|
689
|
-
}
|
|
690
|
-
const microphoneTrack = this.stream.getAudioTracks()[0];
|
|
691
|
-
if (microphoneTrack) {
|
|
692
|
-
this._microphoneEnabled = microphoneTrack.enabled;
|
|
693
|
-
this._currentMicrophoneDeviceId = microphoneTrack.getSettings().deviceId;
|
|
694
|
-
}
|
|
1380
|
+
}
|
|
1381
|
+
if (autoSwitchAudioId !== undefined) {
|
|
1382
|
+
const audioDevices = selectLocalMediaDevices(state).filter((d) => d.kind === "audioinput");
|
|
1383
|
+
const audioId = selectCurrentMicrophoneDeviceId(state);
|
|
1384
|
+
let nextAudioId = nextId(audioDevices.filter((d) => !busy.includes(d.deviceId)), audioId);
|
|
1385
|
+
if (!nextAudioId || audioId === nextAudioId) {
|
|
1386
|
+
nextAudioId = nextId(audioDevices, audioId);
|
|
695
1387
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
|
|
1388
|
+
if (audioId !== nextAudioId) {
|
|
1389
|
+
autoSwitchAudioId = nextAudioId;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
if (autoSwitchAudioId !== undefined || autoSwitchVideoId !== undefined) {
|
|
1393
|
+
dispatch(doSwitchLocalStream({ audioId: autoSwitchAudioId, videoId: autoSwitchVideoId }));
|
|
1394
|
+
}
|
|
1395
|
+
return { devices: newDevices };
|
|
1396
|
+
}
|
|
1397
|
+
catch (error) {
|
|
1398
|
+
return rejectWithValue(error);
|
|
1399
|
+
}
|
|
1400
|
+
}));
|
|
1401
|
+
const doSwitchLocalStream = createAppAsyncThunk("localMedia/doSwitchLocalStream", ({ audioId, videoId }, { dispatch, getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1402
|
+
const state = getState();
|
|
1403
|
+
const replaceStream = selectLocalMediaStream(state);
|
|
1404
|
+
const constraintsOptions = selectLocalMediaConstraintsOptions(state);
|
|
1405
|
+
const onlySwitchingOne = !!(videoId && !audioId) || !!(!videoId && audioId);
|
|
1406
|
+
if (!replaceStream) {
|
|
1407
|
+
// Switching no stream makes no sense
|
|
1408
|
+
return;
|
|
701
1409
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
1410
|
+
try {
|
|
1411
|
+
const { replacedTracks } = yield getStream(Object.assign(Object.assign({}, constraintsOptions), { audioId: audioId === undefined ? false : audioId, videoId: videoId === undefined ? false : videoId, type: "exact" }), { replaceStream });
|
|
1412
|
+
const deviceId = audioId || videoId;
|
|
1413
|
+
if (onlySwitchingOne && deviceId) {
|
|
1414
|
+
dispatch(deviceBusy({
|
|
1415
|
+
deviceId,
|
|
1416
|
+
}));
|
|
1417
|
+
}
|
|
1418
|
+
return { replacedTracks };
|
|
1419
|
+
}
|
|
1420
|
+
catch (error) {
|
|
1421
|
+
console.error(error);
|
|
1422
|
+
const deviceId = audioId || videoId;
|
|
1423
|
+
if (onlySwitchingOne && deviceId) {
|
|
1424
|
+
dispatch(deviceBusy({
|
|
1425
|
+
deviceId,
|
|
1426
|
+
}));
|
|
708
1427
|
}
|
|
1428
|
+
return rejectWithValue(error);
|
|
1429
|
+
}
|
|
1430
|
+
}));
|
|
1431
|
+
const doStartLocalMedia = createAppAsyncThunk("localMedia/doStartLocalMedia", (payload, { getState, dispatch, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1432
|
+
const onDeviceChange = debounce(() => {
|
|
1433
|
+
dispatch(doUpdateDeviceList());
|
|
1434
|
+
}, { delay: 500 });
|
|
1435
|
+
if (global.navigator.mediaDevices) {
|
|
1436
|
+
navigator.mediaDevices.addEventListener("devicechange", onDeviceChange);
|
|
1437
|
+
}
|
|
1438
|
+
// Resolve if existing stream is passed
|
|
1439
|
+
if ("getTracks" in payload) {
|
|
1440
|
+
return Promise.resolve({ stream: payload, onDeviceChange });
|
|
1441
|
+
}
|
|
1442
|
+
if (!(payload.audio || payload.video)) {
|
|
1443
|
+
return { stream: new MediaStream(), onDeviceChange };
|
|
1444
|
+
}
|
|
1445
|
+
else {
|
|
1446
|
+
dispatch(setLocalMediaOptions({ options: payload }));
|
|
1447
|
+
}
|
|
1448
|
+
try {
|
|
1449
|
+
// then update devices
|
|
1450
|
+
yield dispatch(doUpdateDeviceList());
|
|
1451
|
+
// then get new state
|
|
1452
|
+
const state = getState();
|
|
1453
|
+
const constraintsOptions = selectLocalMediaConstraintsOptions(state);
|
|
1454
|
+
const { stream } = yield getStream(Object.assign(Object.assign({}, constraintsOptions), { audioId: payload.audio, videoId: payload.video }));
|
|
1455
|
+
return { stream, onDeviceChange };
|
|
1456
|
+
}
|
|
1457
|
+
catch (error) {
|
|
1458
|
+
return rejectWithValue(error);
|
|
1459
|
+
}
|
|
1460
|
+
}));
|
|
1461
|
+
const doStopLocalMedia = createAppThunk(() => (dispatch, getState) => {
|
|
1462
|
+
const stream = selectLocalMediaStream(getState());
|
|
1463
|
+
const onDeviceChange = selectLocalMediaRaw(getState()).onDeviceChange;
|
|
1464
|
+
stream === null || stream === void 0 ? void 0 : stream.getTracks().forEach((track) => {
|
|
1465
|
+
track.stop();
|
|
1466
|
+
});
|
|
1467
|
+
if (global.navigator.mediaDevices && onDeviceChange) {
|
|
1468
|
+
navigator.mediaDevices.removeEventListener("devicechange", onDeviceChange);
|
|
709
1469
|
}
|
|
710
|
-
|
|
1470
|
+
dispatch(localMediaStopped());
|
|
1471
|
+
});
|
|
1472
|
+
/**
|
|
1473
|
+
* Selectors
|
|
1474
|
+
*/
|
|
1475
|
+
const selectBusyDeviceIds = (state) => state.localMedia.busyDeviceIds;
|
|
1476
|
+
const selectCameraDeviceError = (state) => state.localMedia.cameraDeviceError;
|
|
1477
|
+
const selectCurrentCameraDeviceId = (state) => state.localMedia.currentCameraDeviceId;
|
|
1478
|
+
const selectCurrentMicrophoneDeviceId = (state) => state.localMedia.currentMicrophoneDeviceId;
|
|
1479
|
+
const selectIsCameraEnabled = (state) => state.localMedia.cameraEnabled;
|
|
1480
|
+
const selectIsMicrophoneEnabled = (state) => state.localMedia.microphoneEnabled;
|
|
1481
|
+
const selectIsSettingCameraDevice = (state) => state.localMedia.isSettingCameraDevice;
|
|
1482
|
+
const selectIsSettingMicrophoneDevice = (state) => state.localMedia.isSettingMicrophoneDevice;
|
|
1483
|
+
const selectIsToggleCamera = (state) => state.localMedia.isTogglingCamera;
|
|
1484
|
+
const selectLocalMediaDevices = (state) => state.localMedia.devices;
|
|
1485
|
+
const selectLocalMediaOptions = (state) => state.localMedia.options;
|
|
1486
|
+
const selectLocalMediaOwnsStream = createSelector(selectLocalMediaOptions, (options) => !!options);
|
|
1487
|
+
const selectLocalMediaRaw = (state) => state.localMedia;
|
|
1488
|
+
const selectLocalMediaStatus = (state) => state.localMedia.status;
|
|
1489
|
+
const selectLocalMediaStream = (state) => state.localMedia.stream;
|
|
1490
|
+
const selectMicrophoneDeviceError = (state) => state.localMedia.microphoneDeviceError;
|
|
1491
|
+
const selectLocalMediaStartError = (state) => state.localMedia.startError;
|
|
1492
|
+
const selectLocalMediaIsSwitchingStream = (state) => state.localMedia.isSwitchingStream;
|
|
1493
|
+
const selectLocalMediaConstraintsOptions = createSelector(selectLocalMediaDevices, (devices) => ({
|
|
1494
|
+
devices,
|
|
1495
|
+
options: {
|
|
1496
|
+
disableAEC: false,
|
|
1497
|
+
disableAGC: false,
|
|
1498
|
+
hd: true,
|
|
1499
|
+
lax: false,
|
|
1500
|
+
lowDataMode: false,
|
|
1501
|
+
simulcast: true,
|
|
1502
|
+
widescreen: true,
|
|
1503
|
+
},
|
|
1504
|
+
}));
|
|
1505
|
+
const selectIsLocalMediaStarting = createSelector(selectLocalMediaStatus, (status) => status === "starting");
|
|
1506
|
+
const selectCameraDevices = createSelector(selectLocalMediaDevices, selectBusyDeviceIds, (devices, busyDeviceIds) => devices.filter((d) => d.kind === "videoinput").filter((d) => !busyDeviceIds.includes(d.deviceId)));
|
|
1507
|
+
const selectMicrophoneDevices = createSelector(selectLocalMediaDevices, selectBusyDeviceIds, (devices, busyDeviceIds) => devices.filter((d) => d.kind === "audioinput").filter((d) => !busyDeviceIds.includes(d.deviceId)));
|
|
1508
|
+
const selectSpeakerDevices = createSelector(selectLocalMediaDevices, (devices) => devices.filter((d) => d.kind === "audiooutput"));
|
|
1509
|
+
/**
|
|
1510
|
+
* Reactors
|
|
1511
|
+
*/
|
|
1512
|
+
// Start localMedia unless started when roomConnection is wanted
|
|
1513
|
+
const selectLocalMediaShouldStartWithOptions = createSelector(selectAppWantsToJoin, selectLocalMediaStatus, selectLocalMediaOptions, (appWantsToJoin, localMediaStatus, localMediaOptions) => {
|
|
1514
|
+
if (appWantsToJoin && localMediaStatus === "" && localMediaOptions) {
|
|
1515
|
+
return localMediaOptions;
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
createReactor([selectLocalMediaShouldStartWithOptions], ({ dispatch }, options) => {
|
|
1519
|
+
if (options) {
|
|
1520
|
+
dispatch(doStartLocalMedia(options));
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1523
|
+
// Stop localMedia when roomConnection is no longer wanted and media was started when joining
|
|
1524
|
+
const selectLocalMediaShouldStop = createSelector(selectAppWantsToJoin, selectLocalMediaStatus, selectLocalMediaOptions, (appWantsToJoin, localMediaStatus, localMediaOptions) => {
|
|
1525
|
+
return !appWantsToJoin && localMediaStatus !== "" && !!localMediaOptions;
|
|
1526
|
+
});
|
|
1527
|
+
createReactor([selectLocalMediaShouldStop], ({ dispatch }, localMediaShouldStop) => {
|
|
1528
|
+
if (localMediaShouldStop) {
|
|
1529
|
+
dispatch(doStopLocalMedia());
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
startAppListening({
|
|
1533
|
+
predicate: (_action, currentState, previousState) => {
|
|
1534
|
+
const oldValue = selectIsMicrophoneEnabled(previousState);
|
|
1535
|
+
const newValue = selectIsMicrophoneEnabled(currentState);
|
|
1536
|
+
const isReady = selectLocalMediaStatus(previousState) === "started";
|
|
1537
|
+
return isReady && oldValue !== newValue;
|
|
1538
|
+
},
|
|
1539
|
+
effect: (_, { dispatch }) => {
|
|
1540
|
+
dispatch(doToggleMicrophone());
|
|
1541
|
+
},
|
|
1542
|
+
});
|
|
1543
|
+
startAppListening({
|
|
1544
|
+
predicate: (_action, currentState, previousState) => {
|
|
1545
|
+
const isToggling = selectIsToggleCamera(currentState);
|
|
1546
|
+
if (isToggling) {
|
|
1547
|
+
return false;
|
|
1548
|
+
}
|
|
1549
|
+
const oldValue = selectIsCameraEnabled(previousState);
|
|
1550
|
+
const newValue = selectIsCameraEnabled(currentState);
|
|
1551
|
+
const isReady = selectLocalMediaStatus(previousState) === "started";
|
|
1552
|
+
return isReady && oldValue !== newValue;
|
|
1553
|
+
},
|
|
1554
|
+
effect: (_action, { dispatch }) => {
|
|
1555
|
+
dispatch(doToggleCamera());
|
|
1556
|
+
},
|
|
1557
|
+
});
|
|
1558
|
+
startAppListening({
|
|
1559
|
+
predicate: (_action, currentState, previousState) => {
|
|
1560
|
+
const oldValue = selectCurrentCameraDeviceId(previousState);
|
|
1561
|
+
const newValue = selectCurrentCameraDeviceId(currentState);
|
|
1562
|
+
const isReady = selectLocalMediaStatus(previousState) === "started";
|
|
1563
|
+
return isReady && oldValue !== newValue;
|
|
1564
|
+
},
|
|
1565
|
+
effect: (_action, { dispatch }) => {
|
|
1566
|
+
dispatch(doSetDevice({ audio: false, video: true }));
|
|
1567
|
+
},
|
|
1568
|
+
});
|
|
1569
|
+
startAppListening({
|
|
1570
|
+
predicate: (_action, currentState, previousState) => {
|
|
1571
|
+
const oldValue = selectCurrentMicrophoneDeviceId(previousState);
|
|
1572
|
+
const newValue = selectCurrentMicrophoneDeviceId(currentState);
|
|
1573
|
+
const isReady = selectLocalMediaStatus(previousState) === "started";
|
|
1574
|
+
return isReady && oldValue !== newValue;
|
|
1575
|
+
},
|
|
1576
|
+
effect: (_action, { dispatch }) => {
|
|
1577
|
+
dispatch(doSetDevice({ audio: true, video: false }));
|
|
1578
|
+
},
|
|
1579
|
+
});
|
|
1580
|
+
startAppListening({
|
|
1581
|
+
matcher: isAnyOf(doStartLocalMedia.fulfilled, doUpdateDeviceList.fulfilled, doSwitchLocalStream.fulfilled, doSwitchLocalStream.rejected),
|
|
1582
|
+
effect: (_action, { dispatch, getState }) => {
|
|
1583
|
+
const state = getState();
|
|
1584
|
+
const stream = selectLocalMediaStream(state);
|
|
1585
|
+
const devices = selectLocalMediaDevices(state);
|
|
1586
|
+
if (!stream)
|
|
1587
|
+
return;
|
|
1588
|
+
const deviceData = getDeviceData({
|
|
1589
|
+
audioTrack: stream.getAudioTracks()[0],
|
|
1590
|
+
videoTrack: stream.getVideoTracks()[0],
|
|
1591
|
+
devices,
|
|
1592
|
+
});
|
|
1593
|
+
dispatch(localStreamMetadataUpdated(deviceData));
|
|
1594
|
+
},
|
|
1595
|
+
});
|
|
711
1596
|
|
|
712
|
-
const initialState$
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
startError: null,
|
|
1597
|
+
const initialState$a = {
|
|
1598
|
+
displayName: "",
|
|
1599
|
+
id: "",
|
|
1600
|
+
isAudioEnabled: true,
|
|
1601
|
+
isVideoEnabled: true,
|
|
1602
|
+
isLocalParticipant: true,
|
|
1603
|
+
stream: undefined,
|
|
1604
|
+
isScreenSharing: false,
|
|
1605
|
+
roleName: "",
|
|
722
1606
|
};
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1607
|
+
const doEnableAudio = createAppAsyncThunk("localParticipant/doEnableAudio", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1608
|
+
const state = getState();
|
|
1609
|
+
const socket = selectSignalConnectionRaw(state).socket;
|
|
1610
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("enable_audio", { enabled: payload.enabled });
|
|
1611
|
+
return payload.enabled;
|
|
1612
|
+
}));
|
|
1613
|
+
const doEnableVideo = createAppAsyncThunk("localParticipant/doEnableVideo", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1614
|
+
const state = getState();
|
|
1615
|
+
const socket = selectSignalConnectionRaw(state).socket;
|
|
1616
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("enable_video", { enabled: payload.enabled });
|
|
1617
|
+
return payload.enabled;
|
|
1618
|
+
}));
|
|
1619
|
+
const doSetDisplayName = createAppAsyncThunk("localParticipant/doSetDisplayName", (payload, { getState }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1620
|
+
const state = getState();
|
|
1621
|
+
const socket = selectSignalConnectionRaw(state).socket;
|
|
1622
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("send_client_metadata", {
|
|
1623
|
+
type: "UserData",
|
|
1624
|
+
payload,
|
|
1625
|
+
});
|
|
1626
|
+
return payload.displayName;
|
|
1627
|
+
}));
|
|
1628
|
+
const localParticipantSlice = createSlice({
|
|
1629
|
+
name: "localParticipant",
|
|
1630
|
+
initialState: initialState$a,
|
|
1631
|
+
reducers: {
|
|
1632
|
+
doSetLocalParticipant: (state, action) => {
|
|
726
1633
|
return Object.assign(Object.assign({}, state), action.payload);
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
return Object.assign(Object.assign({}, state), { isSettingCameraDevice: false });
|
|
733
|
-
case "SET_CAMERA_DEVICE_ERROR":
|
|
734
|
-
return Object.assign(Object.assign({}, state), { cameraDeviceError: action.payload, isSettingCameraDevice: false });
|
|
735
|
-
case "SET_MICROPHONE_DEVICE":
|
|
736
|
-
return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: true, microphoneDeviceError: null });
|
|
737
|
-
case "SET_MICROPHONE_DEVICE_COMPLETE":
|
|
738
|
-
return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: false });
|
|
739
|
-
case "SET_MICROPHONE_DEVICE_ERROR":
|
|
740
|
-
return Object.assign(Object.assign({}, state), { isSettingMicrophoneDevice: false, microphoneDeviceError: action.payload });
|
|
741
|
-
case "START":
|
|
742
|
-
return Object.assign(Object.assign({}, state), { isStarting: true, startError: null });
|
|
743
|
-
case "START_COMPLETE":
|
|
744
|
-
return Object.assign(Object.assign({}, state), { isStarting: false });
|
|
745
|
-
case "START_ERROR":
|
|
746
|
-
return Object.assign(Object.assign({}, state), { isStarting: false, startError: action.payload });
|
|
747
|
-
default:
|
|
748
|
-
return state;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
function useLocalMedia(optionsOrStream = { audio: true, video: true }) {
|
|
752
|
-
const [localMedia] = useState(() => new LocalMedia(optionsOrStream));
|
|
753
|
-
const [state, dispatch] = useReducer(reducer$1, initialState$1);
|
|
754
|
-
useEffect(() => {
|
|
755
|
-
localMedia.addEventListener("device_list_updated", (e) => {
|
|
756
|
-
const { cameraDevices, microphoneDevices, speakerDevices } = e.detail;
|
|
757
|
-
dispatch({ type: "DEVICE_LIST_UPDATED", payload: { cameraDevices, microphoneDevices, speakerDevices } });
|
|
1634
|
+
},
|
|
1635
|
+
},
|
|
1636
|
+
extraReducers: (builder) => {
|
|
1637
|
+
builder.addCase(doAppJoin, (state, action) => {
|
|
1638
|
+
return Object.assign(Object.assign({}, state), { displayName: action.payload.displayName });
|
|
758
1639
|
});
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
dispatch({
|
|
762
|
-
type: "LOCAL_STREAM_UPDATED",
|
|
763
|
-
payload: {
|
|
764
|
-
stream,
|
|
765
|
-
currentCameraDeviceId: localMedia.getCameraDeviceId(),
|
|
766
|
-
currentMicrophoneDeviceId: localMedia.getMicrophoneDeviceId(),
|
|
767
|
-
},
|
|
768
|
-
});
|
|
1640
|
+
builder.addCase(doEnableAudio.fulfilled, (state, action) => {
|
|
1641
|
+
return Object.assign(Object.assign({}, state), { isAudioEnabled: action.payload });
|
|
769
1642
|
});
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
try {
|
|
773
|
-
yield localMedia.start();
|
|
774
|
-
dispatch({ type: "START_COMPLETE" });
|
|
775
|
-
}
|
|
776
|
-
catch (error) {
|
|
777
|
-
dispatch({ type: "START_ERROR", payload: error });
|
|
778
|
-
}
|
|
1643
|
+
builder.addCase(doEnableVideo.fulfilled, (state, action) => {
|
|
1644
|
+
return Object.assign(Object.assign({}, state), { isVideoEnabled: action.payload });
|
|
779
1645
|
});
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
return localMedia.toggleCameraEnabled(...args);
|
|
811
|
-
},
|
|
812
|
-
toggleMicrophoneEnabled: (...args) => {
|
|
813
|
-
return localMedia.toggleMichrophoneEnabled(...args);
|
|
814
|
-
},
|
|
815
|
-
},
|
|
816
|
-
_ref: localMedia,
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
const EVENTS = {
|
|
821
|
-
CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
|
|
822
|
-
STREAM_ADDED: "stream_added",
|
|
823
|
-
RTC_MANAGER_CREATED: "rtc_manager_created",
|
|
824
|
-
RTC_MANAGER_DESTROYED: "rtc_manager_destroyed",
|
|
825
|
-
LOCAL_STREAM_TRACK_ADDED: "local_stream_track_added",
|
|
826
|
-
LOCAL_STREAM_TRACK_REMOVED: "local_stream_track_removed",
|
|
827
|
-
REMOTE_STREAM_TRACK_ADDED: "remote_stream_track_added",
|
|
828
|
-
REMOTE_STREAM_TRACK_REMOVED: "remote_stream_track_removed",
|
|
829
|
-
};
|
|
1646
|
+
builder.addCase(doSetDisplayName.fulfilled, (state, action) => {
|
|
1647
|
+
return Object.assign(Object.assign({}, state), { displayName: action.payload });
|
|
1648
|
+
});
|
|
1649
|
+
builder.addCase(signalEvents.roomJoined, (state, action) => {
|
|
1650
|
+
var _a, _b;
|
|
1651
|
+
const client = (_b = (_a = action.payload) === null || _a === void 0 ? void 0 : _a.room) === null || _b === void 0 ? void 0 : _b.clients.find((c) => { var _a; return c.id === ((_a = action.payload) === null || _a === void 0 ? void 0 : _a.selfId); });
|
|
1652
|
+
return Object.assign(Object.assign({}, state), { id: action.payload.selfId, roleName: (client === null || client === void 0 ? void 0 : client.role.roleName) || "" });
|
|
1653
|
+
});
|
|
1654
|
+
},
|
|
1655
|
+
});
|
|
1656
|
+
localParticipantSlice.actions;
|
|
1657
|
+
const selectLocalParticipantRaw = (state) => state.localParticipant;
|
|
1658
|
+
const selectSelfId = (state) => state.localParticipant.id;
|
|
1659
|
+
const selectLocalParticipantRole = (state) => state.localParticipant.roleName;
|
|
1660
|
+
startAppListening({
|
|
1661
|
+
actionCreator: toggleCameraEnabled,
|
|
1662
|
+
effect: ({ payload }, { dispatch, getState }) => {
|
|
1663
|
+
const { enabled } = payload;
|
|
1664
|
+
const { isVideoEnabled } = selectLocalParticipantRaw(getState());
|
|
1665
|
+
dispatch(doEnableVideo({ enabled: enabled || !isVideoEnabled }));
|
|
1666
|
+
},
|
|
1667
|
+
});
|
|
1668
|
+
startAppListening({
|
|
1669
|
+
actionCreator: toggleMicrophoneEnabled,
|
|
1670
|
+
effect: ({ payload }, { dispatch, getState }) => {
|
|
1671
|
+
const { enabled } = payload;
|
|
1672
|
+
const { isAudioEnabled } = selectLocalParticipantRaw(getState());
|
|
1673
|
+
dispatch(doEnableAudio({ enabled: enabled || !isAudioEnabled }));
|
|
1674
|
+
},
|
|
1675
|
+
});
|
|
830
1676
|
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1677
|
+
const initialState$9 = {
|
|
1678
|
+
status: "",
|
|
1679
|
+
stream: null,
|
|
1680
|
+
error: null,
|
|
1681
|
+
};
|
|
1682
|
+
/**
|
|
1683
|
+
* Reducer
|
|
1684
|
+
*/
|
|
1685
|
+
const localScreenshareSlice = createSlice({
|
|
1686
|
+
name: "localScreenshare",
|
|
1687
|
+
initialState: initialState$9,
|
|
1688
|
+
reducers: {
|
|
1689
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1690
|
+
stopScreenshare(state, action) {
|
|
1691
|
+
return Object.assign(Object.assign({}, state), { status: "", stream: null });
|
|
1692
|
+
},
|
|
1693
|
+
},
|
|
1694
|
+
extraReducers: (builder) => {
|
|
1695
|
+
builder.addCase(doStartScreenshare.pending, (state) => {
|
|
1696
|
+
return Object.assign(Object.assign({}, state), { status: "starting" });
|
|
1697
|
+
});
|
|
1698
|
+
builder.addCase(doStartScreenshare.fulfilled, (state, { payload: { stream } }) => {
|
|
1699
|
+
return Object.assign(Object.assign({}, state), { status: "active", stream });
|
|
1700
|
+
});
|
|
1701
|
+
builder.addCase(doStartScreenshare.rejected, (state, { payload }) => {
|
|
1702
|
+
return Object.assign(Object.assign({}, state), { error: payload, status: "", stream: null });
|
|
1703
|
+
});
|
|
1704
|
+
},
|
|
1705
|
+
});
|
|
1706
|
+
/**
|
|
1707
|
+
* Action creators
|
|
1708
|
+
*/
|
|
1709
|
+
const { stopScreenshare } = localScreenshareSlice.actions;
|
|
1710
|
+
const doStartScreenshare = createAppAsyncThunk("localScreenshare/doStartScreenshare", (_, { dispatch, getState, rejectWithValue }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1711
|
+
var _a;
|
|
1712
|
+
try {
|
|
1713
|
+
const state = getState();
|
|
1714
|
+
const screenshareStream = selectLocalScreenshareStream(state);
|
|
1715
|
+
if (screenshareStream) {
|
|
1716
|
+
return { stream: screenshareStream };
|
|
1717
|
+
}
|
|
1718
|
+
const stream = yield navigator.mediaDevices.getDisplayMedia();
|
|
1719
|
+
const onEnded = () => {
|
|
1720
|
+
dispatch(doStopScreenshare());
|
|
1721
|
+
};
|
|
1722
|
+
if ("oninactive" in stream) {
|
|
1723
|
+
// Chrome
|
|
1724
|
+
stream.addEventListener("inactive", onEnded);
|
|
1725
|
+
}
|
|
1726
|
+
else {
|
|
1727
|
+
// Firefox
|
|
1728
|
+
(_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.addEventListener("ended", onEnded);
|
|
1729
|
+
}
|
|
1730
|
+
return { stream };
|
|
1731
|
+
}
|
|
1732
|
+
catch (error) {
|
|
1733
|
+
return rejectWithValue(error);
|
|
1734
|
+
}
|
|
1735
|
+
}));
|
|
1736
|
+
const doStopScreenshare = createAppThunk(() => (dispatch, getState) => {
|
|
1737
|
+
const state = getState();
|
|
1738
|
+
const screenshareStream = selectLocalScreenshareStream(state);
|
|
1739
|
+
if (!screenshareStream) {
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
screenshareStream.getTracks().forEach((track) => track.stop());
|
|
1743
|
+
dispatch(stopScreenshare({ stream: screenshareStream }));
|
|
1744
|
+
});
|
|
1745
|
+
const selectLocalScreenshareStream = (state) => state.localScreenshare.stream;
|
|
1746
|
+
/**
|
|
1747
|
+
* Reactors
|
|
1748
|
+
*/
|
|
1749
|
+
startAppListening({
|
|
1750
|
+
actionCreator: localMediaStopped,
|
|
1751
|
+
effect: (_, { getState }) => {
|
|
1752
|
+
const state = getState();
|
|
1753
|
+
const screenshareStream = selectLocalScreenshareStream(state);
|
|
1754
|
+
if (!screenshareStream) {
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
screenshareStream === null || screenshareStream === void 0 ? void 0 : screenshareStream.getTracks().forEach((track) => {
|
|
1758
|
+
track.stop();
|
|
1759
|
+
});
|
|
1760
|
+
},
|
|
1761
|
+
});
|
|
837
1762
|
|
|
838
|
-
|
|
1763
|
+
const initialState$8 = {
|
|
1764
|
+
data: null,
|
|
1765
|
+
isFetching: false,
|
|
1766
|
+
error: null,
|
|
1767
|
+
};
|
|
1768
|
+
const organizationSlice = createSlice({
|
|
1769
|
+
initialState: initialState$8,
|
|
1770
|
+
name: "organization",
|
|
1771
|
+
reducers: {},
|
|
1772
|
+
extraReducers: (builder) => {
|
|
1773
|
+
builder.addCase(doOrganizationFetch.pending, (state) => {
|
|
1774
|
+
return Object.assign(Object.assign({}, state), { isFetching: true });
|
|
1775
|
+
});
|
|
1776
|
+
builder.addCase(doOrganizationFetch.fulfilled, (state, action) => {
|
|
1777
|
+
if (!action.payload)
|
|
1778
|
+
return Object.assign(Object.assign({}, state), { isFetching: true });
|
|
1779
|
+
return Object.assign(Object.assign({}, state), { isFetching: false, data: action.payload });
|
|
1780
|
+
});
|
|
1781
|
+
builder.addCase(doOrganizationFetch.rejected, (state) => {
|
|
1782
|
+
return Object.assign(Object.assign({}, state), { isFetching: false, error: true });
|
|
1783
|
+
});
|
|
1784
|
+
},
|
|
1785
|
+
});
|
|
1786
|
+
/**
|
|
1787
|
+
* Action creators
|
|
1788
|
+
*/
|
|
1789
|
+
const doOrganizationFetch = createAppAsyncThunk("organization/doOrganizationFetch", (_, { extra, getState }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1790
|
+
try {
|
|
1791
|
+
const roomUrl = selectAppRoomUrl(getState());
|
|
1792
|
+
const organization = yield extra.services.fetchOrganizationFromRoomUrl(roomUrl || "");
|
|
1793
|
+
if (!organization) {
|
|
1794
|
+
throw new Error("Invalid room url");
|
|
1795
|
+
}
|
|
1796
|
+
return organization;
|
|
1797
|
+
}
|
|
1798
|
+
catch (error) {
|
|
1799
|
+
console.error(error);
|
|
1800
|
+
}
|
|
1801
|
+
}));
|
|
1802
|
+
/**
|
|
1803
|
+
* Selectors
|
|
1804
|
+
*/
|
|
1805
|
+
const selectOrganizationRaw = (state) => state.organization;
|
|
1806
|
+
const selectOrganizationId = (state) => { var _a; return (_a = state.organization.data) === null || _a === void 0 ? void 0 : _a.organizationId; };
|
|
1807
|
+
/**
|
|
1808
|
+
* Reducers
|
|
1809
|
+
*/
|
|
1810
|
+
const selectShouldFetchOrganization = createSelector(selectAppWantsToJoin, selectOrganizationRaw, selectDeviceCredentialsRaw, (wantsToJoin, organization, deviceCredentials) => {
|
|
1811
|
+
if (wantsToJoin &&
|
|
1812
|
+
!organization.data &&
|
|
1813
|
+
!organization.isFetching &&
|
|
1814
|
+
!organization.error &&
|
|
1815
|
+
!deviceCredentials.isFetching) {
|
|
1816
|
+
return true;
|
|
1817
|
+
}
|
|
1818
|
+
return false;
|
|
1819
|
+
});
|
|
1820
|
+
createReactor([selectShouldFetchOrganization], ({ dispatch }, shouldFetchOrganization) => {
|
|
1821
|
+
if (shouldFetchOrganization) {
|
|
1822
|
+
dispatch(doOrganizationFetch());
|
|
1823
|
+
}
|
|
1824
|
+
});
|
|
839
1825
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1826
|
+
function createRtcEventAction(name) {
|
|
1827
|
+
return createAction(`rtcConnection/event/${name}`);
|
|
1828
|
+
}
|
|
1829
|
+
const rtcEvents = {
|
|
1830
|
+
rtcManagerCreated: createRtcEventAction("rtcManagerCreated"),
|
|
1831
|
+
rtcManagerDestroyed: createRtcEventAction("rtcManagerDestroyed"),
|
|
1832
|
+
streamAdded: createRtcEventAction("streamAdded"),
|
|
1833
|
+
};
|
|
1834
|
+
|
|
1835
|
+
const NON_PERSON_ROLES = ["recorder", "streamer"];
|
|
1836
|
+
/**
|
|
1837
|
+
* State mapping utils
|
|
1838
|
+
*/
|
|
1839
|
+
function createParticipant(client, newJoiner = false) {
|
|
1840
|
+
const { streams } = client, rest = __rest(client, ["streams"]);
|
|
1841
|
+
return Object.assign(Object.assign({}, rest), { stream: null, streams: streams.map((streamId) => ({ id: streamId, state: newJoiner ? "new_accept" : "to_accept" })), isLocalParticipant: false, presentationStream: null, newJoiner });
|
|
1842
|
+
}
|
|
1843
|
+
function findParticipant(state, participantId) {
|
|
1844
|
+
const index = state.remoteParticipants.findIndex((c) => c.id === participantId);
|
|
1845
|
+
return { index, participant: state.remoteParticipants[index] };
|
|
1846
|
+
}
|
|
1847
|
+
function updateParticipant(state, participantId, updates) {
|
|
1848
|
+
const { participant, index } = findParticipant(state, participantId);
|
|
1849
|
+
if (!participant) {
|
|
1850
|
+
console.error(`Did not find client for update ${participantId}`);
|
|
1851
|
+
return state;
|
|
1852
|
+
}
|
|
1853
|
+
return Object.assign(Object.assign({}, state), { remoteParticipants: [
|
|
1854
|
+
...state.remoteParticipants.slice(0, index),
|
|
1855
|
+
Object.assign(Object.assign({}, participant), updates),
|
|
1856
|
+
...state.remoteParticipants.slice(index + 1),
|
|
1857
|
+
] });
|
|
1858
|
+
}
|
|
1859
|
+
function addParticipant(state, participant) {
|
|
1860
|
+
const { participant: foundParticipant } = findParticipant(state, participant.id);
|
|
1861
|
+
if (foundParticipant) {
|
|
1862
|
+
console.warn(`Client already existing ${participant.id}. Ignoring`);
|
|
1863
|
+
return state;
|
|
1864
|
+
}
|
|
1865
|
+
return Object.assign(Object.assign({}, state), { remoteParticipants: [...state.remoteParticipants, participant] });
|
|
1866
|
+
}
|
|
1867
|
+
function updateStreamState(state, participantId, streamId, state_) {
|
|
1868
|
+
const { participant } = findParticipant(state, participantId);
|
|
1869
|
+
if (!participant) {
|
|
1870
|
+
console.error(`No client ${participantId} found to update stream ${streamId} ${state_}`);
|
|
1871
|
+
return state;
|
|
1872
|
+
}
|
|
1873
|
+
const idIdx = participant.streams.findIndex((s) => s.id === streamId);
|
|
1874
|
+
const streams = [...participant.streams];
|
|
1875
|
+
streams[idIdx] = Object.assign(Object.assign({}, streams[idIdx]), { state: state_ });
|
|
1876
|
+
return updateParticipant(state, participantId, { streams });
|
|
1877
|
+
}
|
|
1878
|
+
function removeClient(state, participantId) {
|
|
1879
|
+
return Object.assign(Object.assign({}, state), { remoteParticipants: state.remoteParticipants.filter((c) => c.id !== participantId) });
|
|
1880
|
+
}
|
|
1881
|
+
function addStreamId(state, participantId, streamId) {
|
|
1882
|
+
const { participant } = findParticipant(state, participantId);
|
|
1883
|
+
if (!participant || participant.streams.find((s) => s.id === streamId)) {
|
|
1884
|
+
console.warn(`No participant ${participantId} or stream ${streamId} already exists`);
|
|
1885
|
+
return state;
|
|
1886
|
+
}
|
|
1887
|
+
return updateParticipant(state, participantId, {
|
|
1888
|
+
streams: [...participant.streams, { id: streamId, state: "to_accept" }],
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
function removeStreamId(state, participantId, streamId) {
|
|
1892
|
+
var _a, _b;
|
|
1893
|
+
const { participant } = findParticipant(state, participantId);
|
|
1894
|
+
if (!participant) {
|
|
1895
|
+
console.error(`No participant ${participantId} found to remove stream ${streamId}`);
|
|
1896
|
+
return state;
|
|
1897
|
+
}
|
|
1898
|
+
const currentStreamId = participant.stream && participant.stream.id;
|
|
1899
|
+
const presentationId = ((_a = participant.presentationStream) === null || _a === void 0 ? void 0 : _a.inboundId) || ((_b = participant.presentationStream) === null || _b === void 0 ? void 0 : _b.id);
|
|
1900
|
+
const idIdx = participant.streams.findIndex((s) => s.id === streamId);
|
|
1901
|
+
return updateParticipant(state, participantId, Object.assign(Object.assign({ streams: participant.streams.filter((_, i) => i !== idIdx) }, (currentStreamId === streamId && { stream: null })), (presentationId === streamId && { presentationStream: null })));
|
|
1902
|
+
}
|
|
1903
|
+
function addStream(state, payload) {
|
|
1904
|
+
const { clientId, stream, streamType } = payload;
|
|
1905
|
+
let { streamId } = payload;
|
|
1906
|
+
const { participant } = findParticipant(state, clientId);
|
|
1907
|
+
if (!participant) {
|
|
1908
|
+
console.error(`Did not find client ${clientId} for adding stream`);
|
|
1909
|
+
return state;
|
|
1910
|
+
}
|
|
1911
|
+
const remoteParticipants = state.remoteParticipants;
|
|
1912
|
+
if (!streamId) {
|
|
1913
|
+
streamId = stream.id;
|
|
1914
|
+
}
|
|
1915
|
+
const remoteParticipant = remoteParticipants.find((p) => p.id === clientId);
|
|
1916
|
+
if (!remoteParticipant) {
|
|
1917
|
+
return state;
|
|
1918
|
+
}
|
|
1919
|
+
const remoteParticipantStream = remoteParticipant.streams.find((s) => s.id === streamId);
|
|
1920
|
+
if ((remoteParticipant.stream &&
|
|
1921
|
+
(remoteParticipant.stream.id === streamId || remoteParticipant.stream.inboundId === streamId)) ||
|
|
1922
|
+
(!remoteParticipant.stream && streamType === "webcam") ||
|
|
1923
|
+
(!remoteParticipant.stream && !streamType && !remoteParticipantStream)) {
|
|
1924
|
+
return updateParticipant(state, clientId, { stream });
|
|
1925
|
+
}
|
|
1926
|
+
// screen share
|
|
1927
|
+
return updateParticipant(state, clientId, {
|
|
1928
|
+
presentationStream: stream,
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
const initialState$7 = {
|
|
1932
|
+
remoteParticipants: [],
|
|
1933
|
+
};
|
|
1934
|
+
const remoteParticipantsSlice = createSlice({
|
|
1935
|
+
name: "remoteParticipants",
|
|
1936
|
+
initialState: initialState$7,
|
|
1937
|
+
reducers: {
|
|
1938
|
+
streamStatusUpdated: (state, action) => {
|
|
1939
|
+
let newState = state;
|
|
1940
|
+
for (const { clientId, streamId, state } of action.payload) {
|
|
1941
|
+
newState = updateStreamState(newState, clientId, streamId, state);
|
|
1942
|
+
}
|
|
1943
|
+
return newState;
|
|
1944
|
+
},
|
|
1945
|
+
participantStreamAdded: (state, action) => {
|
|
1946
|
+
const { clientId, stream } = action.payload;
|
|
1947
|
+
return updateParticipant(state, clientId, {
|
|
1948
|
+
stream,
|
|
1949
|
+
});
|
|
1950
|
+
},
|
|
1951
|
+
participantStreamIdAdded: (state, action) => {
|
|
1952
|
+
const { clientId, streamId } = action.payload;
|
|
1953
|
+
return addStreamId(state, clientId, streamId);
|
|
1954
|
+
},
|
|
1955
|
+
},
|
|
1956
|
+
extraReducers: (builder) => {
|
|
1957
|
+
builder.addCase(signalEvents.roomJoined, (state, action) => {
|
|
1958
|
+
var _a;
|
|
1959
|
+
if (!((_a = action.payload) === null || _a === void 0 ? void 0 : _a.room))
|
|
1960
|
+
return state;
|
|
1961
|
+
const selfId = action.payload.selfId;
|
|
1962
|
+
const { clients } = action.payload.room;
|
|
1963
|
+
return Object.assign(Object.assign({}, state), { remoteParticipants: clients
|
|
1964
|
+
.filter((c) => c.id !== selfId)
|
|
1965
|
+
.filter((c) => !NON_PERSON_ROLES.includes(c.role.roleName))
|
|
1966
|
+
.map((c) => createParticipant(c)) });
|
|
1967
|
+
});
|
|
1968
|
+
builder.addCase(rtcEvents.streamAdded, (state, action) => {
|
|
1969
|
+
return addStream(state, action.payload);
|
|
1970
|
+
});
|
|
1971
|
+
builder.addCase(signalEvents.newClient, (state, action) => {
|
|
1972
|
+
const { client } = action.payload;
|
|
1973
|
+
if (NON_PERSON_ROLES.includes(client.role.roleName)) {
|
|
1974
|
+
return state;
|
|
1975
|
+
}
|
|
1976
|
+
return addParticipant(state, createParticipant(client, true));
|
|
1977
|
+
});
|
|
1978
|
+
builder.addCase(signalEvents.clientLeft, (state, action) => {
|
|
1979
|
+
const { clientId } = action.payload;
|
|
1980
|
+
return removeClient(state, clientId);
|
|
1981
|
+
});
|
|
1982
|
+
builder.addCase(signalEvents.audioEnabled, (state, action) => {
|
|
1983
|
+
const { clientId, isAudioEnabled } = action.payload;
|
|
1984
|
+
return updateParticipant(state, clientId, {
|
|
1985
|
+
isAudioEnabled,
|
|
1986
|
+
});
|
|
1987
|
+
});
|
|
1988
|
+
builder.addCase(signalEvents.videoEnabled, (state, action) => {
|
|
1989
|
+
const { clientId, isVideoEnabled } = action.payload;
|
|
1990
|
+
return updateParticipant(state, clientId, {
|
|
1991
|
+
isVideoEnabled,
|
|
1992
|
+
});
|
|
1993
|
+
});
|
|
1994
|
+
builder.addCase(signalEvents.clientMetadataReceived, (state, action) => {
|
|
1995
|
+
const { clientId, displayName } = action.payload.payload;
|
|
1996
|
+
return updateParticipant(state, clientId, {
|
|
1997
|
+
displayName,
|
|
1998
|
+
});
|
|
1999
|
+
});
|
|
2000
|
+
builder.addCase(signalEvents.screenshareStarted, (state, action) => {
|
|
2001
|
+
const { clientId, streamId } = action.payload;
|
|
2002
|
+
return addStreamId(state, clientId, streamId);
|
|
2003
|
+
});
|
|
2004
|
+
builder.addCase(signalEvents.screenshareStopped, (state, action) => {
|
|
2005
|
+
const { clientId, streamId } = action.payload;
|
|
2006
|
+
return removeStreamId(state, clientId, streamId);
|
|
2007
|
+
});
|
|
2008
|
+
},
|
|
2009
|
+
});
|
|
2010
|
+
/**
|
|
2011
|
+
* Action creators
|
|
2012
|
+
*/
|
|
2013
|
+
const { participantStreamAdded, participantStreamIdAdded, streamStatusUpdated } = remoteParticipantsSlice.actions;
|
|
2014
|
+
const selectRemoteParticipants = (state) => state.remoteParticipants.remoteParticipants;
|
|
2015
|
+
const selectScreenshares = createSelector(selectLocalScreenshareStream, selectRemoteParticipants, (localScreenshareStream, remoteParticipants) => {
|
|
2016
|
+
const screenshares = [];
|
|
2017
|
+
if (localScreenshareStream) {
|
|
2018
|
+
screenshares.push({
|
|
2019
|
+
id: localScreenshareStream.id,
|
|
2020
|
+
participantId: "local",
|
|
2021
|
+
hasAudioTrack: localScreenshareStream.getAudioTracks().length > 0,
|
|
2022
|
+
stream: localScreenshareStream,
|
|
2023
|
+
isLocal: true,
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
for (const participant of remoteParticipants) {
|
|
2027
|
+
if (participant.presentationStream) {
|
|
2028
|
+
screenshares.push({
|
|
2029
|
+
id: participant.presentationStream.id,
|
|
2030
|
+
participantId: participant.id,
|
|
2031
|
+
hasAudioTrack: participant.presentationStream.getAudioTracks().length > 0,
|
|
2032
|
+
stream: participant.presentationStream,
|
|
2033
|
+
isLocal: false,
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return screenshares;
|
|
2038
|
+
});
|
|
2039
|
+
|
|
2040
|
+
const initialState$6 = {
|
|
2041
|
+
status: "initializing",
|
|
2042
|
+
error: null,
|
|
2043
|
+
};
|
|
2044
|
+
const roomConnectionSlice = createSlice({
|
|
2045
|
+
initialState: initialState$6,
|
|
2046
|
+
name: "roomConnection",
|
|
2047
|
+
reducers: {
|
|
2048
|
+
connectionStatusChanged: (state, action) => {
|
|
2049
|
+
return Object.assign(Object.assign({}, state), { status: action.payload });
|
|
2050
|
+
},
|
|
2051
|
+
},
|
|
2052
|
+
extraReducers: (builder) => {
|
|
2053
|
+
builder.addCase(signalEvents.roomJoined, (state, action) => {
|
|
2054
|
+
//TODO: Handle error
|
|
2055
|
+
const { error, isLocked } = action.payload;
|
|
2056
|
+
if (error === "room_locked" && isLocked) {
|
|
2057
|
+
return Object.assign(Object.assign({}, state), { status: "room_locked" });
|
|
2058
|
+
}
|
|
2059
|
+
return Object.assign(Object.assign({}, state), { status: "connected" });
|
|
2060
|
+
});
|
|
2061
|
+
builder.addCase(socketReconnecting, (state) => {
|
|
2062
|
+
return Object.assign(Object.assign({}, state), { status: "reconnect" });
|
|
2063
|
+
});
|
|
2064
|
+
},
|
|
2065
|
+
});
|
|
2066
|
+
/**
|
|
2067
|
+
* Action creators
|
|
2068
|
+
*/
|
|
2069
|
+
const { connectionStatusChanged } = roomConnectionSlice.actions;
|
|
2070
|
+
const doKnockRoom = createAppThunk(() => (dispatch, getState) => {
|
|
2071
|
+
const state = getState();
|
|
2072
|
+
const socket = selectSignalConnectionRaw(state).socket;
|
|
2073
|
+
const roomName = selectAppRoomName(state);
|
|
2074
|
+
const roomKey = selectAppRoomKey(state);
|
|
2075
|
+
const displayName = selectAppDisplayName(state);
|
|
2076
|
+
const sdkVersion = selectAppSdkVersion(state);
|
|
2077
|
+
const externalId = selectAppExternalId(state);
|
|
2078
|
+
const organizationId = selectOrganizationId(state);
|
|
2079
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("knock_room", {
|
|
2080
|
+
avatarUrl: null,
|
|
2081
|
+
config: {
|
|
2082
|
+
isAudioEnabled: true,
|
|
2083
|
+
isVideoEnabled: true,
|
|
2084
|
+
},
|
|
2085
|
+
deviceCapabilities: { canScreenshare: true },
|
|
2086
|
+
displayName,
|
|
2087
|
+
isCoLocated: false,
|
|
2088
|
+
isDevicePermissionDenied: false,
|
|
2089
|
+
kickFromOtherRooms: false,
|
|
2090
|
+
organizationId,
|
|
2091
|
+
roomKey,
|
|
2092
|
+
roomName,
|
|
2093
|
+
selfId: "",
|
|
2094
|
+
userAgent: `browser-sdk:${sdkVersion || "unknown"}`,
|
|
2095
|
+
externalId,
|
|
2096
|
+
});
|
|
2097
|
+
dispatch(connectionStatusChanged("knocking"));
|
|
2098
|
+
});
|
|
2099
|
+
const doConnectRoom = createAppThunk(() => (dispatch, getState) => {
|
|
2100
|
+
const state = getState();
|
|
2101
|
+
const socket = selectSignalConnectionRaw(state).socket;
|
|
2102
|
+
const roomName = selectAppRoomName(state);
|
|
2103
|
+
const roomKey = selectAppRoomKey(state);
|
|
2104
|
+
const displayName = selectAppDisplayName(state);
|
|
2105
|
+
const sdkVersion = selectAppSdkVersion(state);
|
|
2106
|
+
const externalId = selectAppExternalId(state);
|
|
2107
|
+
const organizationId = selectOrganizationId(state);
|
|
2108
|
+
const isCameraEnabled = selectIsCameraEnabled(getState());
|
|
2109
|
+
const isMicrophoneEnabled = selectIsMicrophoneEnabled(getState());
|
|
2110
|
+
const selfId = selectSelfId(getState());
|
|
2111
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("join_room", {
|
|
2112
|
+
avatarUrl: null,
|
|
2113
|
+
config: {
|
|
2114
|
+
isAudioEnabled: isMicrophoneEnabled,
|
|
2115
|
+
isVideoEnabled: isCameraEnabled,
|
|
2116
|
+
},
|
|
2117
|
+
deviceCapabilities: { canScreenshare: true },
|
|
2118
|
+
displayName,
|
|
2119
|
+
isCoLocated: false,
|
|
2120
|
+
isDevicePermissionDenied: false,
|
|
2121
|
+
kickFromOtherRooms: false,
|
|
2122
|
+
organizationId,
|
|
2123
|
+
roomKey,
|
|
2124
|
+
roomName,
|
|
2125
|
+
selfId,
|
|
2126
|
+
userAgent: `browser-sdk:${sdkVersion || "unknown"}`,
|
|
2127
|
+
externalId,
|
|
2128
|
+
});
|
|
2129
|
+
dispatch(connectionStatusChanged("connecting"));
|
|
2130
|
+
});
|
|
2131
|
+
const selectRoomConnectionStatus = (state) => state.roomConnection.status;
|
|
2132
|
+
/**
|
|
2133
|
+
* Reactors
|
|
2134
|
+
*/
|
|
2135
|
+
const selectShouldConnectRoom = createSelector([selectOrganizationId, selectRoomConnectionStatus, selectSignalConnectionDeviceIdentified, selectLocalMediaStatus], (hasOrganizationIdFetched, roomConnectionStatus, signalConnectionDeviceIdentified, localMediaStatus) => {
|
|
2136
|
+
if (localMediaStatus === "started" &&
|
|
2137
|
+
signalConnectionDeviceIdentified &&
|
|
2138
|
+
!!hasOrganizationIdFetched &&
|
|
2139
|
+
["initializing", "reconnect"].includes(roomConnectionStatus)) {
|
|
2140
|
+
return true;
|
|
2141
|
+
}
|
|
2142
|
+
return false;
|
|
2143
|
+
});
|
|
2144
|
+
createReactor([selectShouldConnectRoom], ({ dispatch }, shouldConnectRoom) => {
|
|
2145
|
+
if (shouldConnectRoom) {
|
|
2146
|
+
dispatch(doConnectRoom());
|
|
2147
|
+
}
|
|
2148
|
+
});
|
|
2149
|
+
startAppListening({
|
|
2150
|
+
actionCreator: signalEvents.knockHandled,
|
|
2151
|
+
effect: ({ payload }, { dispatch, getState }) => {
|
|
2152
|
+
const { clientId, resolution } = payload;
|
|
2153
|
+
const state = getState();
|
|
2154
|
+
const selfId = selectSelfId(state);
|
|
2155
|
+
if (clientId !== selfId) {
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
if (resolution === "accepted") {
|
|
2159
|
+
dispatch(setRoomKey(payload.metadata.roomKey));
|
|
2160
|
+
dispatch(doConnectRoom());
|
|
2161
|
+
}
|
|
2162
|
+
else if (resolution === "rejected") {
|
|
2163
|
+
dispatch(connectionStatusChanged("knock_rejected"));
|
|
2164
|
+
}
|
|
2165
|
+
},
|
|
2166
|
+
});
|
|
2167
|
+
|
|
2168
|
+
const EVENTS = {
|
|
2169
|
+
CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
|
|
2170
|
+
STREAM_ADDED: "stream_added",
|
|
2171
|
+
RTC_MANAGER_CREATED: "rtc_manager_created",
|
|
2172
|
+
RTC_MANAGER_DESTROYED: "rtc_manager_destroyed",
|
|
2173
|
+
LOCAL_STREAM_TRACK_ADDED: "local_stream_track_added",
|
|
2174
|
+
LOCAL_STREAM_TRACK_REMOVED: "local_stream_track_removed",
|
|
2175
|
+
REMOTE_STREAM_TRACK_ADDED: "remote_stream_track_added",
|
|
2176
|
+
REMOTE_STREAM_TRACK_REMOVED: "remote_stream_track_removed",
|
|
2177
|
+
};
|
|
2178
|
+
|
|
2179
|
+
const TYPES = {
|
|
2180
|
+
CONNECTING: "connecting",
|
|
2181
|
+
CONNECTION_FAILED: "connection_failed",
|
|
2182
|
+
CONNECTION_SUCCESSFUL: "connection_successful",
|
|
2183
|
+
CONNECTION_DISCONNECTED: "connection_disconnected",
|
|
2184
|
+
};
|
|
2185
|
+
|
|
2186
|
+
// Protocol enum used for the CLIENT (Make sure to keep it in sync with its server counterpart)
|
|
2187
|
+
|
|
2188
|
+
// Requests: messages from the client to the server
|
|
2189
|
+
const PROTOCOL_REQUESTS = {
|
|
2190
|
+
BLOCK_CLIENT: "block_client",
|
|
2191
|
+
CLAIM_ROOM: "claim_room",
|
|
2192
|
+
CLEAR_CHAT_HISTORY: "clear_chat_history",
|
|
2193
|
+
ENABLE_AUDIO: "enable_audio",
|
|
2194
|
+
ENABLE_VIDEO: "enable_video",
|
|
2195
|
+
END_STREAM: "end_stream",
|
|
2196
|
+
FETCH_MEDIASERVER_CONFIG: "fetch_mediaserver_config",
|
|
2197
|
+
HANDLE_KNOCK: "handle_knock",
|
|
2198
|
+
IDENTIFY_DEVICE: "identify_device",
|
|
2199
|
+
INVITE_CLIENT_AS_MEMBER: "invite_client_as_member",
|
|
2200
|
+
JOIN_ROOM: "join_room",
|
|
2201
|
+
KICK_CLIENT: "kick_client",
|
|
2202
|
+
KNOCK_ROOM: "knock_room",
|
|
2203
|
+
LEAVE_ROOM: "leave_room",
|
|
2204
|
+
SEND_CLIENT_METADATA: "send_client_metadata",
|
|
2205
|
+
SET_LOCK: "set_lock",
|
|
2206
|
+
SHARE_MEDIA: "share_media",
|
|
2207
|
+
START_NEW_STREAM: "start_new_stream",
|
|
2208
|
+
START_SCREENSHARE: "start_screenshare",
|
|
2209
|
+
STOP_SCREENSHARE: "stop_screenshare",
|
|
2210
|
+
START_URL_EMBED: "start_url_embed",
|
|
2211
|
+
STOP_URL_EMBED: "stop_url_embed",
|
|
2212
|
+
START_RECORDING: "start_recording",
|
|
2213
|
+
STOP_RECORDING: "stop_recording",
|
|
2214
|
+
SFU_TOKEN: "sfu_token",
|
|
867
2215
|
};
|
|
868
2216
|
|
|
869
2217
|
// Responses: messages from the server to the client, in response to requests
|
|
@@ -1000,187 +2348,61 @@ class RtcStream {
|
|
|
1000
2348
|
}
|
|
1001
2349
|
}
|
|
1002
2350
|
|
|
1003
|
-
const DEFAULT_SOCKET_PATH = "/protocol/socket.io/v4";
|
|
1004
|
-
|
|
1005
2351
|
/**
|
|
1006
|
-
*
|
|
2352
|
+
* Detect mic issue which seems to happen on OSX when the computer is woken up and sleeping
|
|
2353
|
+
* frequently. A browser restart fixes this.
|
|
2354
|
+
*
|
|
2355
|
+
* Should be called after the connection has been up for a while.
|
|
2356
|
+
*
|
|
2357
|
+
* @see Bug report {@link https://bugs.chromium.org/p/webrtc/issues/detail?id=4799}
|
|
1007
2358
|
*/
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
timeout: 5000,
|
|
1016
|
-
transports: ["websocket"],
|
|
1017
|
-
withCredentials: true,
|
|
1018
|
-
...optionsOverrides,
|
|
1019
|
-
});
|
|
1020
|
-
this._socket.io.on("reconnect", () => {
|
|
1021
|
-
this._socket.sendBuffer = [];
|
|
1022
|
-
});
|
|
1023
|
-
this._socket.io.on("reconnect_attempt", () => {
|
|
1024
|
-
if (this._wasConnectedUsingWebsocket) {
|
|
1025
|
-
this._socket.io.opts.transports = ["websocket"];
|
|
1026
|
-
// only fallback to polling if not safari
|
|
1027
|
-
// safari doesn't support cross doamin cookies making load-balancer stickiness not work
|
|
1028
|
-
// and if socket.io reconnects to another signal instance with polling it will fail
|
|
1029
|
-
// remove if we move signal to a whereby.com subdomain
|
|
1030
|
-
if (adapter.browserDetails.browser !== "safari") delete this._wasConnectedUsingWebsocket;
|
|
1031
|
-
} else {
|
|
1032
|
-
this._socket.io.opts.transports = ["websocket", "polling"];
|
|
1033
|
-
}
|
|
1034
|
-
});
|
|
1035
|
-
this._socket.on("connect", () => {
|
|
1036
|
-
const transport = this.getTransport();
|
|
1037
|
-
if (transport === "websocket") {
|
|
1038
|
-
this._wasConnectedUsingWebsocket = true;
|
|
1039
|
-
}
|
|
1040
|
-
});
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
connect() {
|
|
1044
|
-
if (this.isConnected() || this.isConnecting()) {
|
|
1045
|
-
return;
|
|
1046
|
-
}
|
|
1047
|
-
this._socket.open();
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
disconnect() {
|
|
1051
|
-
this._socket.disconnect();
|
|
2359
|
+
function detectMicrophoneNotWorking(pc) {
|
|
2360
|
+
if (
|
|
2361
|
+
adapter.browserDetails.browser !== "chrome" ||
|
|
2362
|
+
adapter.browserDetails.browser < 58 || // legacy getStats is no longer supported.
|
|
2363
|
+
pc.signalingState === "closed"
|
|
2364
|
+
) {
|
|
2365
|
+
return Promise.resolve(false);
|
|
1052
2366
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
2367
|
+
const sendingAudio = pc.getSenders().some((sender) => sender.track && sender.track.kind === "audio");
|
|
2368
|
+
const receivingAudio = pc.getReceivers().some((receiver) => receiver.track && receiver.track.kind === "audio");
|
|
2369
|
+
return pc.getStats(null).then((result) => {
|
|
2370
|
+
let microphoneFailed = false;
|
|
2371
|
+
result.forEach((report) => {
|
|
2372
|
+
if (
|
|
2373
|
+
report.type === "outbound-rtp" &&
|
|
2374
|
+
(report.kind === "audio" || report.mediaType === "audio") &&
|
|
2375
|
+
sendingAudio
|
|
2376
|
+
) {
|
|
2377
|
+
if (report.bytesSent === 0) {
|
|
2378
|
+
microphoneFailed = "outbound";
|
|
2379
|
+
}
|
|
2380
|
+
} else if (
|
|
2381
|
+
report.type === "inbound-rtp" &&
|
|
2382
|
+
(report.kind === "audio" || report.mediaType === "audio") &&
|
|
2383
|
+
receivingAudio
|
|
2384
|
+
) {
|
|
2385
|
+
if (report.bytesReceived === 0) {
|
|
2386
|
+
microphoneFailed = "inbound";
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
1057
2389
|
});
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
this._socket.emit.apply(this._socket, arguments);
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
emitIfConnected(eventName, data) {
|
|
1065
|
-
if (!this.isConnected()) {
|
|
1066
|
-
return;
|
|
1067
|
-
}
|
|
1068
|
-
this.emit(eventName, data);
|
|
1069
|
-
}
|
|
2390
|
+
return microphoneFailed;
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
1070
2393
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
2394
|
+
var rtcManagerEvents = {
|
|
2395
|
+
CAMERA_NOT_WORKING: "camera_not_working",
|
|
2396
|
+
CONNECTION_BLOCKED_BY_NETWORK: "connection_blocked_by_network",
|
|
2397
|
+
MICROPHONE_NOT_WORKING: "microphone_not_working",
|
|
2398
|
+
MICROPHONE_STOPPED_WORKING: "microphone_stopped_working",
|
|
2399
|
+
SFU_CONNECTION_CLOSED: "sfu_connection_closed",
|
|
2400
|
+
COLOCATION_SPEAKER: "colocation_speaker",
|
|
2401
|
+
DOMINANT_SPEAKER: "dominant_speaker",
|
|
2402
|
+
};
|
|
1080
2403
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
isConnecting() {
|
|
1086
|
-
return this._socket && this._socket.connecting;
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
isConnected() {
|
|
1090
|
-
return this._socket && this._socket.connected;
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
/**
|
|
1094
|
-
* Register a new event handler.
|
|
1095
|
-
*
|
|
1096
|
-
* @param {string} eventName - Name of the event to listen for.
|
|
1097
|
-
* @param {function} handler - The callback function that should be called for the event.
|
|
1098
|
-
* @returns {function} Function to deregister the listener.
|
|
1099
|
-
*/
|
|
1100
|
-
on(eventName, handler) {
|
|
1101
|
-
this._socket.on(eventName, handler);
|
|
1102
|
-
|
|
1103
|
-
return () => {
|
|
1104
|
-
this._socket.off(eventName, handler);
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
/**
|
|
1109
|
-
* Register a new event handler to be triggered only once.
|
|
1110
|
-
*
|
|
1111
|
-
* @param {string} eventName - Name of the event to listen for.
|
|
1112
|
-
* @param {function} handler - The function that should be called for the event.
|
|
1113
|
-
*/
|
|
1114
|
-
once(eventName, handler) {
|
|
1115
|
-
this._socket.once(eventName, handler);
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
/**
|
|
1119
|
-
* Deregister an event handler.
|
|
1120
|
-
*
|
|
1121
|
-
* @param {string} eventName - Name of the event the handler is registered for.
|
|
1122
|
-
* @param {function} handler - The callback that will be deregistered.
|
|
1123
|
-
*/
|
|
1124
|
-
off(eventName, handler) {
|
|
1125
|
-
this._socket.off(eventName, handler);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
/**
|
|
1130
|
-
* Detect mic issue which seems to happen on OSX when the computer is woken up and sleeping
|
|
1131
|
-
* frequently. A browser restart fixes this.
|
|
1132
|
-
*
|
|
1133
|
-
* Should be called after the connection has been up for a while.
|
|
1134
|
-
*
|
|
1135
|
-
* @see Bug report {@link https://bugs.chromium.org/p/webrtc/issues/detail?id=4799}
|
|
1136
|
-
*/
|
|
1137
|
-
function detectMicrophoneNotWorking(pc) {
|
|
1138
|
-
if (
|
|
1139
|
-
adapter.browserDetails.browser !== "chrome" ||
|
|
1140
|
-
adapter.browserDetails.browser < 58 || // legacy getStats is no longer supported.
|
|
1141
|
-
pc.signalingState === "closed"
|
|
1142
|
-
) {
|
|
1143
|
-
return Promise.resolve(false);
|
|
1144
|
-
}
|
|
1145
|
-
const sendingAudio = pc.getSenders().some((sender) => sender.track && sender.track.kind === "audio");
|
|
1146
|
-
const receivingAudio = pc.getReceivers().some((receiver) => receiver.track && receiver.track.kind === "audio");
|
|
1147
|
-
return pc.getStats(null).then((result) => {
|
|
1148
|
-
let microphoneFailed = false;
|
|
1149
|
-
result.forEach((report) => {
|
|
1150
|
-
if (
|
|
1151
|
-
report.type === "outbound-rtp" &&
|
|
1152
|
-
(report.kind === "audio" || report.mediaType === "audio") &&
|
|
1153
|
-
sendingAudio
|
|
1154
|
-
) {
|
|
1155
|
-
if (report.bytesSent === 0) {
|
|
1156
|
-
microphoneFailed = "outbound";
|
|
1157
|
-
}
|
|
1158
|
-
} else if (
|
|
1159
|
-
report.type === "inbound-rtp" &&
|
|
1160
|
-
(report.kind === "audio" || report.mediaType === "audio") &&
|
|
1161
|
-
receivingAudio
|
|
1162
|
-
) {
|
|
1163
|
-
if (report.bytesReceived === 0) {
|
|
1164
|
-
microphoneFailed = "inbound";
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
});
|
|
1168
|
-
return microphoneFailed;
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
var rtcManagerEvents = {
|
|
1173
|
-
CAMERA_NOT_WORKING: "camera_not_working",
|
|
1174
|
-
CONNECTION_BLOCKED_BY_NETWORK: "connection_blocked_by_network",
|
|
1175
|
-
MICROPHONE_NOT_WORKING: "microphone_not_working",
|
|
1176
|
-
MICROPHONE_STOPPED_WORKING: "microphone_stopped_working",
|
|
1177
|
-
SFU_CONNECTION_CLOSED: "sfu_connection_closed",
|
|
1178
|
-
COLOCATION_SPEAKER: "colocation_speaker",
|
|
1179
|
-
DOMINANT_SPEAKER: "dominant_speaker",
|
|
1180
|
-
};
|
|
1181
|
-
|
|
1182
|
-
const browserName$3 = adapter.browserDetails.browser;
|
|
1183
|
-
const browserVersion$1 = adapter.browserDetails.version;
|
|
2404
|
+
const browserName$3 = adapter.browserDetails.browser;
|
|
2405
|
+
const browserVersion$1 = adapter.browserDetails.version;
|
|
1184
2406
|
|
|
1185
2407
|
function setCodecPreferenceSDP(sdp, vp9On, redOn) {
|
|
1186
2408
|
try {
|
|
@@ -5803,6 +7025,580 @@ class RtcManagerDispatcher {
|
|
|
5803
7025
|
}
|
|
5804
7026
|
}
|
|
5805
7027
|
|
|
7028
|
+
const createWebRtcEmitter = (dispatch) => {
|
|
7029
|
+
return {
|
|
7030
|
+
emit: (eventName, data) => {
|
|
7031
|
+
if (eventName === "rtc_manager_created") {
|
|
7032
|
+
dispatch(doRtcManagerCreated(data));
|
|
7033
|
+
}
|
|
7034
|
+
else if (eventName === "stream_added") {
|
|
7035
|
+
dispatch(rtcEvents.streamAdded(data));
|
|
7036
|
+
}
|
|
7037
|
+
else if (eventName === "rtc_manager_destroyed") {
|
|
7038
|
+
dispatch(rtcManagerDestroyed());
|
|
7039
|
+
}
|
|
7040
|
+
else ;
|
|
7041
|
+
},
|
|
7042
|
+
};
|
|
7043
|
+
};
|
|
7044
|
+
const initialState$5 = {
|
|
7045
|
+
dispatcherCreated: false,
|
|
7046
|
+
error: null,
|
|
7047
|
+
isCreatingDispatcher: false,
|
|
7048
|
+
reportedStreamResolutions: {},
|
|
7049
|
+
rtcManager: null,
|
|
7050
|
+
rtcManagerDispatcher: null,
|
|
7051
|
+
rtcManagerInitialized: false,
|
|
7052
|
+
status: "",
|
|
7053
|
+
isAcceptingStreams: false,
|
|
7054
|
+
};
|
|
7055
|
+
const rtcConnectionSlice = createSlice({
|
|
7056
|
+
name: "rtcConnection",
|
|
7057
|
+
initialState: initialState$5,
|
|
7058
|
+
reducers: {
|
|
7059
|
+
isAcceptingStreams: (state, action) => {
|
|
7060
|
+
return Object.assign(Object.assign({}, state), { isAcceptingStreams: action.payload });
|
|
7061
|
+
},
|
|
7062
|
+
resolutionReported: (state, action) => {
|
|
7063
|
+
const { streamId, width, height } = action.payload;
|
|
7064
|
+
return Object.assign(Object.assign({}, state), { reportedStreamResolutions: Object.assign(Object.assign({}, state.reportedStreamResolutions), { [streamId]: { width, height } }) });
|
|
7065
|
+
},
|
|
7066
|
+
rtcDisconnected: () => {
|
|
7067
|
+
return Object.assign({}, initialState$5);
|
|
7068
|
+
},
|
|
7069
|
+
rtcDispatcherCreated: (state, action) => {
|
|
7070
|
+
return Object.assign(Object.assign({}, state), { dispatcherCreated: true, rtcManagerDispatcher: action.payload });
|
|
7071
|
+
},
|
|
7072
|
+
rtcManagerCreated: (state, action) => {
|
|
7073
|
+
return Object.assign(Object.assign({}, state), { rtcManager: action.payload, status: "ready" });
|
|
7074
|
+
},
|
|
7075
|
+
rtcManagerDestroyed: (state) => {
|
|
7076
|
+
return Object.assign(Object.assign({}, state), { rtcManager: null });
|
|
7077
|
+
},
|
|
7078
|
+
rtcManagerInitialized: (state) => {
|
|
7079
|
+
return Object.assign(Object.assign({}, state), { rtcManagerInitialized: true });
|
|
7080
|
+
},
|
|
7081
|
+
},
|
|
7082
|
+
extraReducers: (builder) => {
|
|
7083
|
+
builder.addCase(socketReconnecting, (state) => {
|
|
7084
|
+
return Object.assign(Object.assign({}, state), { status: "reconnect" });
|
|
7085
|
+
});
|
|
7086
|
+
builder.addCase(signalEvents.roomJoined, (state) => {
|
|
7087
|
+
return Object.assign(Object.assign({}, state), { status: state.status === "reconnect" ? "ready" : state.status });
|
|
7088
|
+
});
|
|
7089
|
+
},
|
|
7090
|
+
});
|
|
7091
|
+
/**
|
|
7092
|
+
* Action creators
|
|
7093
|
+
*/
|
|
7094
|
+
const { resolutionReported, rtcDispatcherCreated, rtcDisconnected, rtcManagerCreated, rtcManagerDestroyed, rtcManagerInitialized, isAcceptingStreams, } = rtcConnectionSlice.actions;
|
|
7095
|
+
const doConnectRtc = createAppThunk(() => (dispatch, getState) => {
|
|
7096
|
+
const state = getState();
|
|
7097
|
+
const socket = selectSignalConnectionRaw(state).socket;
|
|
7098
|
+
const dispatcher = selectRtcConnectionRaw(state).rtcManagerDispatcher;
|
|
7099
|
+
const isCameraEnabled = selectIsCameraEnabled(state);
|
|
7100
|
+
const isMicrophoneEnabled = selectIsMicrophoneEnabled(state);
|
|
7101
|
+
if (dispatcher) {
|
|
7102
|
+
return;
|
|
7103
|
+
}
|
|
7104
|
+
const webrtcProvider = {
|
|
7105
|
+
getMediaConstraints: () => ({
|
|
7106
|
+
audio: isMicrophoneEnabled,
|
|
7107
|
+
video: isCameraEnabled,
|
|
7108
|
+
}),
|
|
7109
|
+
deferrable(clientId) {
|
|
7110
|
+
return !clientId;
|
|
7111
|
+
},
|
|
7112
|
+
};
|
|
7113
|
+
const rtcManagerDispatcher = new RtcManagerDispatcher({
|
|
7114
|
+
emitter: createWebRtcEmitter(dispatch),
|
|
7115
|
+
serverSocket: socket,
|
|
7116
|
+
logger: console,
|
|
7117
|
+
webrtcProvider,
|
|
7118
|
+
features: {
|
|
7119
|
+
lowDataModeEnabled: false,
|
|
7120
|
+
sfuServerOverrideHost: undefined,
|
|
7121
|
+
turnServerOverrideHost: undefined,
|
|
7122
|
+
useOnlyTURN: undefined,
|
|
7123
|
+
vp9On: false,
|
|
7124
|
+
h264On: false,
|
|
7125
|
+
simulcastScreenshareOn: false,
|
|
7126
|
+
},
|
|
7127
|
+
});
|
|
7128
|
+
dispatch(rtcDispatcherCreated(rtcManagerDispatcher));
|
|
7129
|
+
});
|
|
7130
|
+
const doDisconnectRtc = createAppThunk(() => (dispatch, getState) => {
|
|
7131
|
+
const { rtcManager } = selectRtcConnectionRaw(getState());
|
|
7132
|
+
if (rtcManager) {
|
|
7133
|
+
rtcManager.disconnectAll();
|
|
7134
|
+
}
|
|
7135
|
+
dispatch(rtcDisconnected());
|
|
7136
|
+
});
|
|
7137
|
+
const doHandleAcceptStreams = createAppThunk((payload) => (dispatch, getState) => {
|
|
7138
|
+
var _a;
|
|
7139
|
+
dispatch(isAcceptingStreams(true));
|
|
7140
|
+
const state = getState();
|
|
7141
|
+
const rtcManager = selectRtcConnectionRaw(state).rtcManager;
|
|
7142
|
+
const remoteParticipants = selectRemoteParticipants(state);
|
|
7143
|
+
if (!rtcManager) {
|
|
7144
|
+
throw new Error("No rtc manager");
|
|
7145
|
+
}
|
|
7146
|
+
const activeBreakout = false;
|
|
7147
|
+
const shouldAcceptNewClients = (_a = rtcManager.shouldAcceptStreamsFromBothSides) === null || _a === void 0 ? void 0 : _a.call(rtcManager);
|
|
7148
|
+
const updates = [];
|
|
7149
|
+
for (const { clientId, streamId, state } of payload) {
|
|
7150
|
+
const participant = remoteParticipants.find((p) => p.id === clientId);
|
|
7151
|
+
if (!participant)
|
|
7152
|
+
continue;
|
|
7153
|
+
if (state === "to_accept" ||
|
|
7154
|
+
(state === "new_accept" && shouldAcceptNewClients) ||
|
|
7155
|
+
(state === "old_accept" && !shouldAcceptNewClients) // these are done to enable broadcast in legacy/p2p
|
|
7156
|
+
) {
|
|
7157
|
+
rtcManager.acceptNewStream({
|
|
7158
|
+
streamId: streamId === "0" ? clientId : streamId,
|
|
7159
|
+
clientId,
|
|
7160
|
+
shouldAddLocalVideo: streamId === "0",
|
|
7161
|
+
activeBreakout,
|
|
7162
|
+
});
|
|
7163
|
+
}
|
|
7164
|
+
else if (state === "new_accept" || state === "old_accept") ;
|
|
7165
|
+
else if (state === "to_unaccept") {
|
|
7166
|
+
rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.disconnect(streamId === "0" ? clientId : streamId, activeBreakout);
|
|
7167
|
+
}
|
|
7168
|
+
else if (state !== "done_accept") {
|
|
7169
|
+
continue;
|
|
7170
|
+
// console.warn(`Stream state not handled: ${state} for ${clientId}-${streamId}`);
|
|
7171
|
+
}
|
|
7172
|
+
else ;
|
|
7173
|
+
updates.push({ clientId, streamId, state: state.replace(/to_|new_|old_/, "done_") });
|
|
7174
|
+
}
|
|
7175
|
+
dispatch(streamStatusUpdated(updates));
|
|
7176
|
+
dispatch(isAcceptingStreams(false));
|
|
7177
|
+
});
|
|
7178
|
+
const doRtcReportStreamResolution = createAppThunk(({ streamId, width, height }) => (dispatch, getState) => {
|
|
7179
|
+
const { reportedStreamResolutions, rtcManager } = selectRtcConnectionRaw(getState());
|
|
7180
|
+
const localStream = selectLocalMediaStream(getState());
|
|
7181
|
+
if (!rtcManager || (localStream === null || localStream === void 0 ? void 0 : localStream.id) === streamId) {
|
|
7182
|
+
return;
|
|
7183
|
+
}
|
|
7184
|
+
const old = reportedStreamResolutions[streamId];
|
|
7185
|
+
if (!old || old.width !== width || old.height !== height) {
|
|
7186
|
+
rtcManager.updateStreamResolution(streamId, null, { width: width || 1, height: height || 1 });
|
|
7187
|
+
}
|
|
7188
|
+
dispatch(resolutionReported({ streamId, width, height }));
|
|
7189
|
+
});
|
|
7190
|
+
const doRtcManagerCreated = createAppThunk((payload) => (dispatch) => {
|
|
7191
|
+
const { rtcManager } = payload;
|
|
7192
|
+
dispatch(rtcManagerCreated(rtcManager));
|
|
7193
|
+
});
|
|
7194
|
+
const doRtcManagerInitialize = createAppThunk(() => (dispatch, getState) => {
|
|
7195
|
+
const localMediaStream = selectLocalMediaStream(getState());
|
|
7196
|
+
const rtcManager = selectRtcConnectionRaw(getState()).rtcManager;
|
|
7197
|
+
const isCameraEnabled = selectIsCameraEnabled(getState());
|
|
7198
|
+
const isMicrophoneEnabled = selectIsMicrophoneEnabled(getState());
|
|
7199
|
+
if (localMediaStream && rtcManager) {
|
|
7200
|
+
rtcManager.addNewStream("0", localMediaStream, !isMicrophoneEnabled, !isCameraEnabled);
|
|
7201
|
+
}
|
|
7202
|
+
dispatch(rtcManagerInitialized());
|
|
7203
|
+
});
|
|
7204
|
+
/**
|
|
7205
|
+
* Selectors
|
|
7206
|
+
*/
|
|
7207
|
+
const selectRtcConnectionRaw = (state) => state.rtcConnection;
|
|
7208
|
+
const selectRtcManagerInitialized = (state) => state.rtcConnection.rtcManagerInitialized;
|
|
7209
|
+
const selectRtcManager = (state) => state.rtcConnection.rtcManager;
|
|
7210
|
+
const selectRtcDispatcherCreated = (state) => state.rtcConnection.dispatcherCreated;
|
|
7211
|
+
const selectRtcIsCreatingDispatcher = (state) => state.rtcConnection.isCreatingDispatcher;
|
|
7212
|
+
const selectRtcStatus = (state) => state.rtcConnection.status;
|
|
7213
|
+
const selectIsAcceptingStreams = (state) => state.rtcConnection.isAcceptingStreams;
|
|
7214
|
+
/**
|
|
7215
|
+
* Reactors
|
|
7216
|
+
*/
|
|
7217
|
+
startAppListening({
|
|
7218
|
+
actionCreator: doSetDevice.fulfilled,
|
|
7219
|
+
effect: ({ payload }, { getState }) => {
|
|
7220
|
+
const { replacedTracks } = payload;
|
|
7221
|
+
const { rtcManager } = selectRtcConnectionRaw(getState());
|
|
7222
|
+
const stream = selectLocalMediaStream(getState());
|
|
7223
|
+
const replace = (kind, oldTrack) => {
|
|
7224
|
+
const track = stream === null || stream === void 0 ? void 0 : stream.getTracks().find((t) => t.kind === kind);
|
|
7225
|
+
return track && (rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.replaceTrack(oldTrack, track));
|
|
7226
|
+
};
|
|
7227
|
+
replacedTracks === null || replacedTracks === void 0 ? void 0 : replacedTracks.forEach((t) => {
|
|
7228
|
+
replace(t.kind, t);
|
|
7229
|
+
});
|
|
7230
|
+
},
|
|
7231
|
+
});
|
|
7232
|
+
startAppListening({
|
|
7233
|
+
actionCreator: doStartScreenshare.fulfilled,
|
|
7234
|
+
effect: ({ payload }, { getState }) => {
|
|
7235
|
+
const { stream } = payload;
|
|
7236
|
+
const { rtcManager } = selectRtcConnectionRaw(getState());
|
|
7237
|
+
rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.addNewStream(stream.id, stream, false, true);
|
|
7238
|
+
},
|
|
7239
|
+
});
|
|
7240
|
+
startAppListening({
|
|
7241
|
+
actionCreator: stopScreenshare,
|
|
7242
|
+
effect: ({ payload }, { getState }) => {
|
|
7243
|
+
const { stream } = payload;
|
|
7244
|
+
const { rtcManager } = selectRtcConnectionRaw(getState());
|
|
7245
|
+
rtcManager === null || rtcManager === void 0 ? void 0 : rtcManager.removeStream(stream.id, stream, null);
|
|
7246
|
+
},
|
|
7247
|
+
});
|
|
7248
|
+
const selectShouldConnectRtc = createSelector(selectRtcDispatcherCreated, selectRtcIsCreatingDispatcher, selectSignalConnectionSocket, (dispatcherCreated, isCreatingDispatcher, signalSocket) => {
|
|
7249
|
+
if (!dispatcherCreated && !isCreatingDispatcher && signalSocket) {
|
|
7250
|
+
return true;
|
|
7251
|
+
}
|
|
7252
|
+
return false;
|
|
7253
|
+
});
|
|
7254
|
+
createReactor([selectShouldConnectRtc], ({ dispatch }, shouldConnectRtc) => {
|
|
7255
|
+
if (shouldConnectRtc) {
|
|
7256
|
+
dispatch(doConnectRtc());
|
|
7257
|
+
}
|
|
7258
|
+
});
|
|
7259
|
+
const selectShouldInitializeRtc = createSelector(selectRtcManager, selectRtcManagerInitialized, selectLocalMediaStatus, (rtcManager, rtcManagerInitialized, localMediaStatus) => {
|
|
7260
|
+
if (localMediaStatus === "started" && rtcManager && !rtcManagerInitialized) {
|
|
7261
|
+
return true;
|
|
7262
|
+
}
|
|
7263
|
+
return false;
|
|
7264
|
+
});
|
|
7265
|
+
createReactor([selectShouldInitializeRtc], ({ dispatch }, shouldInitializeRtc) => {
|
|
7266
|
+
if (shouldInitializeRtc) {
|
|
7267
|
+
dispatch(doRtcManagerInitialize());
|
|
7268
|
+
}
|
|
7269
|
+
});
|
|
7270
|
+
// Disonnect and clean up
|
|
7271
|
+
const selectShouldDisconnectRtc = createSelector(selectRtcStatus, selectAppWantsToJoin, (status, wantsToJoin) => {
|
|
7272
|
+
if (!wantsToJoin && !["", "disconnected"].includes(status)) {
|
|
7273
|
+
return true;
|
|
7274
|
+
}
|
|
7275
|
+
return false;
|
|
7276
|
+
});
|
|
7277
|
+
createReactor([selectShouldDisconnectRtc], ({ dispatch }, shouldDisconnectRtc) => {
|
|
7278
|
+
if (shouldDisconnectRtc) {
|
|
7279
|
+
dispatch(doDisconnectRtc());
|
|
7280
|
+
}
|
|
7281
|
+
});
|
|
7282
|
+
// react accept streams
|
|
7283
|
+
const selectStreamsToAccept = createSelector(selectRtcStatus, selectRemoteParticipants, (rtcStatus, remoteParticipants) => {
|
|
7284
|
+
if (rtcStatus !== "ready") {
|
|
7285
|
+
return [];
|
|
7286
|
+
}
|
|
7287
|
+
const upd = [];
|
|
7288
|
+
// This should actually use remoteClientViews for its handling
|
|
7289
|
+
for (const client of remoteParticipants) {
|
|
7290
|
+
const { streams, id: clientId, newJoiner } = client;
|
|
7291
|
+
for (let i = 0; i < streams.length; i++) {
|
|
7292
|
+
const streamId = streams[i].id;
|
|
7293
|
+
const state = streams[i].state;
|
|
7294
|
+
{
|
|
7295
|
+
// Already connected
|
|
7296
|
+
if (state === "done_accept")
|
|
7297
|
+
continue;
|
|
7298
|
+
upd.push({
|
|
7299
|
+
clientId,
|
|
7300
|
+
streamId,
|
|
7301
|
+
state: `${newJoiner && streamId === "0" ? "new" : "to"}_accept`,
|
|
7302
|
+
});
|
|
7303
|
+
}
|
|
7304
|
+
}
|
|
7305
|
+
}
|
|
7306
|
+
return upd;
|
|
7307
|
+
});
|
|
7308
|
+
createReactor([selectStreamsToAccept, selectIsAcceptingStreams], ({ dispatch }, streamsToAccept, isAcceptingStreams) => {
|
|
7309
|
+
if (0 < streamsToAccept.length && !isAcceptingStreams) {
|
|
7310
|
+
dispatch(doHandleAcceptStreams(streamsToAccept));
|
|
7311
|
+
}
|
|
7312
|
+
});
|
|
7313
|
+
|
|
7314
|
+
const rtcAnalyticsCustomEvents = {
|
|
7315
|
+
audioEnabled: {
|
|
7316
|
+
action: doEnableAudio.fulfilled,
|
|
7317
|
+
rtcEventName: "audioEnabled",
|
|
7318
|
+
getValue: (state) => selectIsMicrophoneEnabled(state),
|
|
7319
|
+
getOutput: (value) => ({ enabled: value }),
|
|
7320
|
+
},
|
|
7321
|
+
videoEnabled: {
|
|
7322
|
+
action: doEnableVideo.fulfilled,
|
|
7323
|
+
rtcEventName: "videoEnabled",
|
|
7324
|
+
getValue: (state) => selectIsCameraEnabled(state),
|
|
7325
|
+
getOutput: (value) => ({ enabled: value }),
|
|
7326
|
+
},
|
|
7327
|
+
localStream: {
|
|
7328
|
+
action: doSetDevice.fulfilled,
|
|
7329
|
+
rtcEventName: "localStream",
|
|
7330
|
+
getValue: (state) => {
|
|
7331
|
+
var _a;
|
|
7332
|
+
return (_a = selectLocalMediaStream(state)) === null || _a === void 0 ? void 0 : _a.getTracks().map((track) => ({ id: track.id, kind: track.kind, label: track.label }));
|
|
7333
|
+
},
|
|
7334
|
+
getOutput: (value) => ({ stream: value }),
|
|
7335
|
+
},
|
|
7336
|
+
localScreenshareStream: {
|
|
7337
|
+
action: doStartScreenshare.fulfilled,
|
|
7338
|
+
rtcEventName: "localScreenshareStream",
|
|
7339
|
+
getValue: (state) => {
|
|
7340
|
+
var _a;
|
|
7341
|
+
return (_a = selectLocalScreenshareStream(state)) === null || _a === void 0 ? void 0 : _a.getTracks().map((track) => ({ id: track.id, kind: track.kind, label: track.label }));
|
|
7342
|
+
},
|
|
7343
|
+
getOutput: (value) => ({ tracks: value }),
|
|
7344
|
+
},
|
|
7345
|
+
localScreenshareStreamStopped: {
|
|
7346
|
+
action: stopScreenshare,
|
|
7347
|
+
rtcEventName: "localScreenshareStream",
|
|
7348
|
+
getValue: () => () => null,
|
|
7349
|
+
getOutput: () => ({}),
|
|
7350
|
+
},
|
|
7351
|
+
displayName: {
|
|
7352
|
+
action: doSetDisplayName.fulfilled,
|
|
7353
|
+
rtcEventName: "displayName",
|
|
7354
|
+
getValue: (state) => selectAppDisplayName(state),
|
|
7355
|
+
getOutput: (value) => ({ displayName: value }),
|
|
7356
|
+
},
|
|
7357
|
+
clientId: {
|
|
7358
|
+
action: null,
|
|
7359
|
+
rtcEventName: "clientId",
|
|
7360
|
+
getValue: (state) => selectSelfId(state),
|
|
7361
|
+
getOutput: (value) => ({ clientId: value }),
|
|
7362
|
+
},
|
|
7363
|
+
deviceId: {
|
|
7364
|
+
action: null,
|
|
7365
|
+
rtcEventName: "deviceId",
|
|
7366
|
+
getValue: (state) => selectDeviceId(state),
|
|
7367
|
+
getOutput: (value) => ({ deviceId: value }),
|
|
7368
|
+
},
|
|
7369
|
+
externalId: {
|
|
7370
|
+
action: null,
|
|
7371
|
+
rtcEventName: "externalId",
|
|
7372
|
+
getValue: (state) => selectAppExternalId(state),
|
|
7373
|
+
getOutput: (value) => ({ externalId: value }),
|
|
7374
|
+
},
|
|
7375
|
+
organizationId: {
|
|
7376
|
+
action: null,
|
|
7377
|
+
rtcEventName: "organizationId",
|
|
7378
|
+
getValue: (state) => selectOrganizationId(state),
|
|
7379
|
+
getOutput: (value) => ({ organizationId: value }),
|
|
7380
|
+
},
|
|
7381
|
+
signalConnectionStatus: {
|
|
7382
|
+
action: null,
|
|
7383
|
+
rtcEventName: "signalConnectionStatus",
|
|
7384
|
+
getValue: (state) => selectSignalStatus(state),
|
|
7385
|
+
getOutput: (value) => ({ status: value }),
|
|
7386
|
+
},
|
|
7387
|
+
rtcConnectionStatus: {
|
|
7388
|
+
action: null,
|
|
7389
|
+
rtcEventName: "rtcConnectionStatus",
|
|
7390
|
+
getValue: (state) => selectRtcStatus(state),
|
|
7391
|
+
getOutput: (value) => ({ status: value }),
|
|
7392
|
+
},
|
|
7393
|
+
userRole: {
|
|
7394
|
+
action: null,
|
|
7395
|
+
rtcEventName: "userRole",
|
|
7396
|
+
getValue: (state) => selectLocalParticipantRole(state),
|
|
7397
|
+
getOutput: (value) => ({ userRole: value }),
|
|
7398
|
+
},
|
|
7399
|
+
};
|
|
7400
|
+
const rtcCustomEventActions = Object.values(rtcAnalyticsCustomEvents)
|
|
7401
|
+
.map(({ action }) => action)
|
|
7402
|
+
.filter((action) => action !== null);
|
|
7403
|
+
const makeComparable = (value) => {
|
|
7404
|
+
if (typeof value === "object")
|
|
7405
|
+
return JSON.stringify(value);
|
|
7406
|
+
return value;
|
|
7407
|
+
};
|
|
7408
|
+
const initialState$4 = {
|
|
7409
|
+
reportedValues: {},
|
|
7410
|
+
};
|
|
7411
|
+
const rtcAnalyticsSlice = createSlice({
|
|
7412
|
+
initialState: initialState$4,
|
|
7413
|
+
name: "rtcAnalytics",
|
|
7414
|
+
reducers: {
|
|
7415
|
+
updateReportedValues(state, action) {
|
|
7416
|
+
return Object.assign(Object.assign({}, state), { reportedValues: Object.assign(Object.assign({}, state.reportedValues), { [action.payload.rtcEventName]: action.payload.value }) });
|
|
7417
|
+
},
|
|
7418
|
+
},
|
|
7419
|
+
});
|
|
7420
|
+
const doRtcAnalyticsCustomEventsInitialize = createAppThunk(() => (dispatch, getState) => {
|
|
7421
|
+
const state = getState();
|
|
7422
|
+
const rtcManager = selectRtcConnectionRaw(state).rtcManager;
|
|
7423
|
+
if (!rtcManager)
|
|
7424
|
+
return;
|
|
7425
|
+
Object.values(rtcAnalyticsCustomEvents).forEach(({ rtcEventName, getValue, getOutput }) => {
|
|
7426
|
+
var _a;
|
|
7427
|
+
const value = getValue(state);
|
|
7428
|
+
const output = Object.assign(Object.assign({}, getOutput(value)), { _time: Date.now() });
|
|
7429
|
+
const comparableValue = makeComparable(value);
|
|
7430
|
+
if (((_a = state.rtcAnalytics.reportedValues) === null || _a === void 0 ? void 0 : _a[rtcEventName]) !== comparableValue) {
|
|
7431
|
+
rtcManager.sendStatsCustomEvent(rtcEventName, output);
|
|
7432
|
+
dispatch(updateReportedValues({ rtcEventName, value }));
|
|
7433
|
+
}
|
|
7434
|
+
});
|
|
7435
|
+
});
|
|
7436
|
+
/**
|
|
7437
|
+
* Action creators
|
|
7438
|
+
*/
|
|
7439
|
+
const { updateReportedValues } = rtcAnalyticsSlice.actions;
|
|
7440
|
+
startAppListening({
|
|
7441
|
+
matcher: isAnyOf(...rtcCustomEventActions),
|
|
7442
|
+
effect: ({ type }, { getState, dispatch }) => {
|
|
7443
|
+
var _a;
|
|
7444
|
+
const state = getState();
|
|
7445
|
+
const rtcManager = selectRtcConnectionRaw(state).rtcManager;
|
|
7446
|
+
if (!rtcManager)
|
|
7447
|
+
return;
|
|
7448
|
+
const rtcCustomEvent = Object.values(rtcAnalyticsCustomEvents).find(({ action }) => (action === null || action === void 0 ? void 0 : action.type) === type);
|
|
7449
|
+
if (!rtcCustomEvent)
|
|
7450
|
+
return;
|
|
7451
|
+
const { getValue, getOutput, rtcEventName } = rtcCustomEvent;
|
|
7452
|
+
const value = getValue(state);
|
|
7453
|
+
const comparableValue = makeComparable(value);
|
|
7454
|
+
const output = Object.assign(Object.assign({}, getOutput(value)), { _time: Date.now() });
|
|
7455
|
+
if (((_a = state.rtcAnalytics.reportedValues) === null || _a === void 0 ? void 0 : _a[rtcEventName]) !== comparableValue) {
|
|
7456
|
+
rtcManager.sendStatsCustomEvent(rtcEventName, output);
|
|
7457
|
+
dispatch(updateReportedValues({ rtcEventName, value }));
|
|
7458
|
+
}
|
|
7459
|
+
},
|
|
7460
|
+
});
|
|
7461
|
+
/**
|
|
7462
|
+
* Reactors
|
|
7463
|
+
*/
|
|
7464
|
+
createReactor([selectRtcManagerInitialized], ({ dispatch }, selectRtcManagerInitialized) => {
|
|
7465
|
+
if (selectRtcManagerInitialized) {
|
|
7466
|
+
dispatch(doRtcAnalyticsCustomEventsInitialize());
|
|
7467
|
+
}
|
|
7468
|
+
});
|
|
7469
|
+
|
|
7470
|
+
const initialState$3 = {
|
|
7471
|
+
isStreaming: false,
|
|
7472
|
+
error: null,
|
|
7473
|
+
startedAt: undefined,
|
|
7474
|
+
};
|
|
7475
|
+
const streamingSlice = createSlice({
|
|
7476
|
+
name: "streaming",
|
|
7477
|
+
initialState: initialState$3,
|
|
7478
|
+
reducers: {
|
|
7479
|
+
doHandleStreamingStarted: (state) => {
|
|
7480
|
+
return Object.assign(Object.assign({}, state), { isStreaming: true, error: null,
|
|
7481
|
+
// We don't have the streaming start time stored on the
|
|
7482
|
+
// server, so we use the current time instead. This gives
|
|
7483
|
+
// an invalid timestamp for "Client B" if "Client A" has
|
|
7484
|
+
// been streaming for a while before "Client B" joins.
|
|
7485
|
+
startedAt: new Date().getTime() });
|
|
7486
|
+
},
|
|
7487
|
+
doHandleStreamingStopped: (state) => {
|
|
7488
|
+
return Object.assign(Object.assign({}, state), { isStreaming: false });
|
|
7489
|
+
},
|
|
7490
|
+
},
|
|
7491
|
+
});
|
|
7492
|
+
/**
|
|
7493
|
+
* Action creators
|
|
7494
|
+
*/
|
|
7495
|
+
streamingSlice.actions;
|
|
7496
|
+
/**
|
|
7497
|
+
* Selectors
|
|
7498
|
+
*/
|
|
7499
|
+
const selectStreamingRaw = (state) => state.streaming;
|
|
7500
|
+
|
|
7501
|
+
const initialState$2 = {
|
|
7502
|
+
waitingParticipants: [],
|
|
7503
|
+
};
|
|
7504
|
+
const waitingParticipantsSlice = createSlice({
|
|
7505
|
+
name: "waitingParticipants",
|
|
7506
|
+
initialState: initialState$2,
|
|
7507
|
+
reducers: {},
|
|
7508
|
+
extraReducers: (builder) => {
|
|
7509
|
+
builder.addCase(signalEvents.roomJoined, (state, { payload }) => {
|
|
7510
|
+
var _a;
|
|
7511
|
+
if ((_a = payload.room) === null || _a === void 0 ? void 0 : _a.knockers.length) {
|
|
7512
|
+
return Object.assign(Object.assign({}, state), { waitingParticipants: payload.room.knockers.map((knocker) => ({
|
|
7513
|
+
id: knocker.clientId,
|
|
7514
|
+
displayName: knocker.displayName,
|
|
7515
|
+
})) });
|
|
7516
|
+
}
|
|
7517
|
+
else {
|
|
7518
|
+
return state;
|
|
7519
|
+
}
|
|
7520
|
+
});
|
|
7521
|
+
builder.addCase(signalEvents.roomKnocked, (state, action) => {
|
|
7522
|
+
const { clientId, displayName } = action.payload;
|
|
7523
|
+
return Object.assign(Object.assign({}, state), { waitingParticipants: [...state.waitingParticipants, { id: clientId, displayName }] });
|
|
7524
|
+
});
|
|
7525
|
+
builder.addCase(signalEvents.knockerLeft, (state, action) => {
|
|
7526
|
+
const { clientId } = action.payload;
|
|
7527
|
+
return Object.assign(Object.assign({}, state), { waitingParticipants: state.waitingParticipants.filter((p) => p.id !== clientId) });
|
|
7528
|
+
});
|
|
7529
|
+
},
|
|
7530
|
+
});
|
|
7531
|
+
/**
|
|
7532
|
+
* Action creators
|
|
7533
|
+
*/
|
|
7534
|
+
const doAcceptWaitingParticipant = createAppThunk((payload) => (dispatch, getState) => {
|
|
7535
|
+
const { participantId } = payload;
|
|
7536
|
+
const state = getState();
|
|
7537
|
+
const socket = selectSignalConnectionSocket(state);
|
|
7538
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("handle_knock", {
|
|
7539
|
+
action: "accept",
|
|
7540
|
+
clientId: participantId,
|
|
7541
|
+
response: {},
|
|
7542
|
+
});
|
|
7543
|
+
});
|
|
7544
|
+
const doRejectWaitingParticipant = createAppThunk((payload) => (dispatch, getState) => {
|
|
7545
|
+
const { participantId } = payload;
|
|
7546
|
+
const state = getState();
|
|
7547
|
+
const socket = selectSignalConnectionSocket(state);
|
|
7548
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("handle_knock", {
|
|
7549
|
+
action: "reject",
|
|
7550
|
+
clientId: participantId,
|
|
7551
|
+
response: {},
|
|
7552
|
+
});
|
|
7553
|
+
});
|
|
7554
|
+
const selectWaitingParticipants = (state) => state.waitingParticipants.waitingParticipants;
|
|
7555
|
+
|
|
7556
|
+
var _a;
|
|
7557
|
+
const IS_DEV = (_a = process.env.REACT_APP_IS_DEV === "true") !== null && _a !== void 0 ? _a : false;
|
|
7558
|
+
const rootReducer = combineReducers({
|
|
7559
|
+
app: appSlice.reducer,
|
|
7560
|
+
chat: chatSlice.reducer,
|
|
7561
|
+
cloudRecording: cloudRecordingSlice.reducer,
|
|
7562
|
+
deviceCredentials: deviceCredentialsSlice.reducer,
|
|
7563
|
+
localMedia: localMediaSlice.reducer,
|
|
7564
|
+
localParticipant: localParticipantSlice.reducer,
|
|
7565
|
+
localScreenshare: localScreenshareSlice.reducer,
|
|
7566
|
+
organization: organizationSlice.reducer,
|
|
7567
|
+
remoteParticipants: remoteParticipantsSlice.reducer,
|
|
7568
|
+
roomConnection: roomConnectionSlice.reducer,
|
|
7569
|
+
rtcAnalytics: rtcAnalyticsSlice.reducer,
|
|
7570
|
+
rtcConnection: rtcConnectionSlice.reducer,
|
|
7571
|
+
signalConnection: signalConnectionSlice.reducer,
|
|
7572
|
+
streaming: streamingSlice.reducer,
|
|
7573
|
+
waitingParticipants: waitingParticipantsSlice.reducer,
|
|
7574
|
+
});
|
|
7575
|
+
const createStore = ({ preloadedState, injectServices, }) => {
|
|
7576
|
+
return configureStore({
|
|
7577
|
+
devTools: IS_DEV,
|
|
7578
|
+
reducer: rootReducer,
|
|
7579
|
+
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
|
|
7580
|
+
thunk: {
|
|
7581
|
+
extraArgument: { services: injectServices },
|
|
7582
|
+
},
|
|
7583
|
+
serializableCheck: false,
|
|
7584
|
+
}).prepend(listenerMiddleware.middleware),
|
|
7585
|
+
preloadedState,
|
|
7586
|
+
});
|
|
7587
|
+
};
|
|
7588
|
+
const observeStore = (store, select, onChange) => {
|
|
7589
|
+
let currentState;
|
|
7590
|
+
function handleChange() {
|
|
7591
|
+
const nextState = select(store.getState());
|
|
7592
|
+
if (nextState !== currentState) {
|
|
7593
|
+
currentState = nextState;
|
|
7594
|
+
onChange(currentState);
|
|
7595
|
+
}
|
|
7596
|
+
}
|
|
7597
|
+
const unsubscribe = store.subscribe(handleChange);
|
|
7598
|
+
handleChange();
|
|
7599
|
+
return unsubscribe;
|
|
7600
|
+
};
|
|
7601
|
+
|
|
5806
7602
|
const defaultSubdomainPattern = /^(?:([^.]+)[.])?((:?[^.]+[.]){1,}[^.]+)$/;
|
|
5807
7603
|
const localstackPattern = /^(?:([^.]+)-)?(ip-[^.]*[.](?:hereby[.]dev|rfc1918[.]disappear[.]at)(?::\d+|))$/;
|
|
5808
7604
|
const localhostPattern = /^(?:([^.]+)[.])?(localhost:?\d*)/;
|
|
@@ -5910,17 +7706,6 @@ function assertInstanceOf(value, type, parameterName) {
|
|
|
5910
7706
|
assert$1.ok(value instanceof type, `${resolvedParameterName}<${type.name}> is required`);
|
|
5911
7707
|
return value;
|
|
5912
7708
|
}
|
|
5913
|
-
/**
|
|
5914
|
-
* Asserts that the provided room name is a valid roomName.
|
|
5915
|
-
*
|
|
5916
|
-
* @param roomName - The roomName to check.
|
|
5917
|
-
* @param {string} [parameterName="roomName"] - The name of the parameter.
|
|
5918
|
-
*/
|
|
5919
|
-
function assertRoomName(roomName, parameterName = "roomName") {
|
|
5920
|
-
assertString(roomName, parameterName);
|
|
5921
|
-
assert$1.ok(typeof roomName === "string" && roomName[0] === "/", `${parameterName} must begin with a '/'`);
|
|
5922
|
-
return roomName;
|
|
5923
|
-
}
|
|
5924
7709
|
/**
|
|
5925
7710
|
* Asserts that the provided array is a valid array.
|
|
5926
7711
|
*
|
|
@@ -5931,22 +7716,6 @@ function assertArray(array, parameterName) {
|
|
|
5931
7716
|
assert$1.ok(Array.isArray(array), `${parameterName}<array> is required`);
|
|
5932
7717
|
return array;
|
|
5933
7718
|
}
|
|
5934
|
-
/**
|
|
5935
|
-
* Asserts that value is one of the values provided in an array
|
|
5936
|
-
*
|
|
5937
|
-
* @param value - The value to check.
|
|
5938
|
-
* @param allowedValues - An array of allowed values
|
|
5939
|
-
* @param {string} parameterName - The name of the parameter.
|
|
5940
|
-
*/
|
|
5941
|
-
function assertOneOf(value, allowedValues, parameterName) {
|
|
5942
|
-
assertTruthy(value, "value");
|
|
5943
|
-
assertArray(allowedValues, "allowedValues");
|
|
5944
|
-
const isAllowed = allowedValues.includes(value);
|
|
5945
|
-
if (!isAllowed) {
|
|
5946
|
-
throw new Error(`${parameterName}<string> must be one of the following: ${allowedValues.join(", ")}`);
|
|
5947
|
-
}
|
|
5948
|
-
return value;
|
|
5949
|
-
}
|
|
5950
7719
|
/**
|
|
5951
7720
|
* Asserts that the provided reference is a record.
|
|
5952
7721
|
*
|
|
@@ -6190,22 +7959,6 @@ function extractString(data, propertyName) {
|
|
|
6190
7959
|
return assertString(record[propertyName], propertyName);
|
|
6191
7960
|
}
|
|
6192
7961
|
const extractNullOrString = nullOrExtract(extractString);
|
|
6193
|
-
/**
|
|
6194
|
-
* Extract a Date from the given Json object.
|
|
6195
|
-
* If the value is not a valid Date, an error is thrown.
|
|
6196
|
-
*
|
|
6197
|
-
* @param data - the object to extract the value from
|
|
6198
|
-
* @param propertyName - the name of the parameter to extract
|
|
6199
|
-
* @returns the extracted value
|
|
6200
|
-
*/
|
|
6201
|
-
function extractDate(data, propertyName) {
|
|
6202
|
-
const dateString = extractString(data, propertyName);
|
|
6203
|
-
const d = new Date(dateString);
|
|
6204
|
-
if (isNaN(d.getTime())) {
|
|
6205
|
-
throw new Error(`Invalid date for ${dateString}`);
|
|
6206
|
-
}
|
|
6207
|
-
return d;
|
|
6208
|
-
}
|
|
6209
7962
|
/**
|
|
6210
7963
|
* Extract an Array from the given Json object.
|
|
6211
7964
|
* If the value is not a valid Array, an error is thrown.
|
|
@@ -6522,43 +8275,6 @@ class CredentialsService extends EventEmitter$1 {
|
|
|
6522
8275
|
}
|
|
6523
8276
|
}
|
|
6524
8277
|
|
|
6525
|
-
const noOrganization = () => Promise.resolve(undefined);
|
|
6526
|
-
/**
|
|
6527
|
-
* Class used for all Whereby organization API calls.
|
|
6528
|
-
*/
|
|
6529
|
-
class OrganizationApiClient {
|
|
6530
|
-
/**
|
|
6531
|
-
* Create an OrganizationApiClient instance.
|
|
6532
|
-
*
|
|
6533
|
-
* @param {Object} options - The options for the OrganizationApiClient.
|
|
6534
|
-
* @param {ApiClient} [options.apiClient] - The apiClient to use.
|
|
6535
|
-
* @param {Function} [options.fetchOrganization] - function that returns a promise with the organization.
|
|
6536
|
-
*/
|
|
6537
|
-
constructor({ apiClient, fetchOrganization = noOrganization, }) {
|
|
6538
|
-
this._apiClient = apiClient;
|
|
6539
|
-
this._fetchOrganization = fetchOrganization;
|
|
6540
|
-
this._apiClient = apiClient;
|
|
6541
|
-
}
|
|
6542
|
-
_callRequestMethod(method, url, options) {
|
|
6543
|
-
assertString(url, "url");
|
|
6544
|
-
assert$1.ok(url[0] === "/", 'url<String> only accepts relative URLs beginning with "/".');
|
|
6545
|
-
assert$1.ok(options, "options are required");
|
|
6546
|
-
return this._fetchOrganization().then((organization) => {
|
|
6547
|
-
if (!organization) {
|
|
6548
|
-
return this._apiClient[method](url, options);
|
|
6549
|
-
}
|
|
6550
|
-
const { organizationId } = organization;
|
|
6551
|
-
return this._apiClient[method](`/organizations/${encodeURIComponent(organizationId)}${url}`, options);
|
|
6552
|
-
});
|
|
6553
|
-
}
|
|
6554
|
-
request(url, options) {
|
|
6555
|
-
return this._callRequestMethod("request", url, options);
|
|
6556
|
-
}
|
|
6557
|
-
requestMultipart(url, options) {
|
|
6558
|
-
return this._callRequestMethod("requestMultipart", url, options);
|
|
6559
|
-
}
|
|
6560
|
-
}
|
|
6561
|
-
|
|
6562
8278
|
class EmbeddedFreeTierStatus {
|
|
6563
8279
|
constructor({ isExhausted, renewsAt, totalMinutesLimit, totalMinutesUsed, }) {
|
|
6564
8280
|
this.isExhausted = isExhausted;
|
|
@@ -6758,1279 +8474,192 @@ class OrganizationService {
|
|
|
6758
8474
|
method: "GET",
|
|
6759
8475
|
})
|
|
6760
8476
|
.then(({ data }) => {
|
|
6761
|
-
return Organization.fromJson(data);
|
|
6762
|
-
})
|
|
6763
|
-
.catch((res) => {
|
|
6764
|
-
if (res instanceof Response) {
|
|
6765
|
-
if (res.status === 404) {
|
|
6766
|
-
return null;
|
|
6767
|
-
}
|
|
6768
|
-
throw new Error(res.statusText);
|
|
6769
|
-
}
|
|
6770
|
-
throw res;
|
|
6771
|
-
});
|
|
6772
|
-
}
|
|
6773
|
-
/**
|
|
6774
|
-
* Retrieves the organizations that contain a user
|
|
6775
|
-
* matching provided the email+code or phoneNumber+code
|
|
6776
|
-
* combination.
|
|
6777
|
-
*/
|
|
6778
|
-
getOrganizationsByContactPoint(options) {
|
|
6779
|
-
const { code } = options;
|
|
6780
|
-
const email = "email" in options ? options.email : null;
|
|
6781
|
-
const phoneNumber = "phoneNumber" in options ? options.phoneNumber : null;
|
|
6782
|
-
assert$1.ok((email || phoneNumber) && !(email && phoneNumber), "either email or phoneNumber is required");
|
|
6783
|
-
assertString(code, "code");
|
|
6784
|
-
const contactPoint = email ? { type: "email", value: email } : { type: "phoneNumber", value: phoneNumber };
|
|
6785
|
-
return this._apiClient
|
|
6786
|
-
.request("/organization-queries", {
|
|
6787
|
-
method: "POST",
|
|
6788
|
-
data: {
|
|
6789
|
-
contactPoint,
|
|
6790
|
-
code,
|
|
6791
|
-
},
|
|
6792
|
-
})
|
|
6793
|
-
.then(({ data }) => {
|
|
6794
|
-
return extractArray(data, "organizations", (organization) => Organization.fromJson(organization));
|
|
6795
|
-
});
|
|
6796
|
-
}
|
|
6797
|
-
/**
|
|
6798
|
-
* Retrieves the organizations that contain a user
|
|
6799
|
-
* matching provided the idToken
|
|
6800
|
-
*/
|
|
6801
|
-
getOrganizationsByIdToken({ idToken }) {
|
|
6802
|
-
assertString(idToken, "idToken");
|
|
6803
|
-
return this._apiClient
|
|
6804
|
-
.request("/organization-queries", {
|
|
6805
|
-
method: "POST",
|
|
6806
|
-
data: {
|
|
6807
|
-
idToken,
|
|
6808
|
-
},
|
|
6809
|
-
})
|
|
6810
|
-
.then(({ data }) => {
|
|
6811
|
-
return extractArray(data, "organizations", (organization) => {
|
|
6812
|
-
return Organization.fromJson(Object.assign({ permissions: {}, limits: {} }, assertRecord(organization, "organization")));
|
|
6813
|
-
});
|
|
6814
|
-
});
|
|
6815
|
-
}
|
|
6816
|
-
/**
|
|
6817
|
-
* Retrieves the organizations containing a user
|
|
6818
|
-
* with either the email or phoneNumber matching the logged in user.
|
|
6819
|
-
*
|
|
6820
|
-
* This is useful for showing the possible organization that the current
|
|
6821
|
-
* user could log in to.
|
|
6822
|
-
*/
|
|
6823
|
-
getOrganizationsByLoggedInUser() {
|
|
6824
|
-
return this._apiClient
|
|
6825
|
-
.request("/user/organizations", {
|
|
6826
|
-
method: "GET",
|
|
6827
|
-
})
|
|
6828
|
-
.then(({ data }) => {
|
|
6829
|
-
return extractArray(data, "organizations", (o) => {
|
|
6830
|
-
return Organization.fromJson(Object.assign({ permissions: {}, limits: {} }, assertRecord(o, "organization")));
|
|
6831
|
-
});
|
|
6832
|
-
});
|
|
6833
|
-
}
|
|
6834
|
-
/**
|
|
6835
|
-
* Checks if a subdomain is available and verifies its format.
|
|
6836
|
-
*/
|
|
6837
|
-
getSubdomainAvailability(subdomain) {
|
|
6838
|
-
assertString(subdomain, "subdomain");
|
|
6839
|
-
return this._apiClient
|
|
6840
|
-
.request(`/organization-subdomains/${encodeURIComponent(subdomain)}/availability`, {
|
|
6841
|
-
method: "GET",
|
|
6842
|
-
})
|
|
6843
|
-
.then(({ data }) => {
|
|
6844
|
-
assertInstanceOf(data, Object, "data");
|
|
6845
|
-
return {
|
|
6846
|
-
status: extractString(data, "status"),
|
|
6847
|
-
};
|
|
6848
|
-
});
|
|
6849
|
-
}
|
|
6850
|
-
/**
|
|
6851
|
-
* Updates preferences of the organization.
|
|
6852
|
-
*/
|
|
6853
|
-
updatePreferences({ organizationId, preferences, }) {
|
|
6854
|
-
assertTruthy(organizationId, "organizationId");
|
|
6855
|
-
assertTruthy(preferences, "preferences");
|
|
6856
|
-
return this._apiClient
|
|
6857
|
-
.request(`/organizations/${encodeURIComponent(organizationId)}/preferences`, {
|
|
6858
|
-
method: "PATCH",
|
|
6859
|
-
data: preferences,
|
|
6860
|
-
})
|
|
6861
|
-
.then(() => undefined);
|
|
6862
|
-
}
|
|
6863
|
-
/**
|
|
6864
|
-
* Delete organization
|
|
6865
|
-
*/
|
|
6866
|
-
deleteOrganization({ organizationId }) {
|
|
6867
|
-
assertTruthy(organizationId, "organizationId");
|
|
6868
|
-
return this._apiClient
|
|
6869
|
-
.request(`/organizations/${encodeURIComponent(organizationId)}`, {
|
|
6870
|
-
method: "DELETE",
|
|
6871
|
-
})
|
|
6872
|
-
.then(() => undefined);
|
|
6873
|
-
}
|
|
6874
|
-
}
|
|
6875
|
-
|
|
6876
|
-
class OrganizationServiceCache {
|
|
6877
|
-
constructor({ organizationService, subdomain }) {
|
|
6878
|
-
this._organizationService = organizationService;
|
|
6879
|
-
this._subdomain = subdomain;
|
|
6880
|
-
this._organizationPromise = null;
|
|
6881
|
-
}
|
|
6882
|
-
initOrganization() {
|
|
6883
|
-
return this.fetchOrganization().then(() => undefined);
|
|
6884
|
-
}
|
|
6885
|
-
fetchOrganization() {
|
|
6886
|
-
if (!this._organizationPromise) {
|
|
6887
|
-
this._organizationPromise = this._organizationService.getOrganizationBySubdomain(this._subdomain);
|
|
6888
|
-
}
|
|
6889
|
-
return this._organizationPromise;
|
|
6890
|
-
}
|
|
6891
|
-
}
|
|
6892
|
-
|
|
6893
|
-
// @ts-nocheck
|
|
6894
|
-
class Room {
|
|
6895
|
-
constructor(properties = {}) {
|
|
6896
|
-
assert$1.ok(properties instanceof Object, "properties<object> must be empty or an object");
|
|
6897
|
-
this.isClaimed = false;
|
|
6898
|
-
this.isBanned = false;
|
|
6899
|
-
this.isLocked = false;
|
|
6900
|
-
this.knockPage = {
|
|
6901
|
-
backgroundImageUrl: null,
|
|
6902
|
-
backgroundThumbnailUrl: null,
|
|
6903
|
-
};
|
|
6904
|
-
this.logoUrl = null;
|
|
6905
|
-
this.backgroundImageUrl = null;
|
|
6906
|
-
this.backgroundThumbnailUrl = null;
|
|
6907
|
-
this.type = null;
|
|
6908
|
-
this.legacyRoomType = null;
|
|
6909
|
-
this.mode = null;
|
|
6910
|
-
this.product = null;
|
|
6911
|
-
this.roomName = null;
|
|
6912
|
-
this.theme = null;
|
|
6913
|
-
this.preferences = {};
|
|
6914
|
-
this.protectedPreferences = {};
|
|
6915
|
-
this.publicProfile = null;
|
|
6916
|
-
// Only allow existing property names to be modified
|
|
6917
|
-
const validProperties = {};
|
|
6918
|
-
Object.getOwnPropertyNames(properties).forEach((prop) => {
|
|
6919
|
-
if (Object.getOwnPropertyNames(this).indexOf(prop) !== -1) {
|
|
6920
|
-
validProperties[prop] = properties[prop];
|
|
6921
|
-
}
|
|
6922
|
-
});
|
|
6923
|
-
if (properties.ownerId !== undefined) {
|
|
6924
|
-
this.ownerId = properties.ownerId;
|
|
6925
|
-
}
|
|
6926
|
-
if (properties.meeting !== undefined) {
|
|
6927
|
-
this.meeting = properties.meeting;
|
|
6928
|
-
}
|
|
6929
|
-
Object.assign(this, validProperties);
|
|
6930
|
-
}
|
|
6931
|
-
}
|
|
6932
|
-
|
|
6933
|
-
class Meeting {
|
|
6934
|
-
constructor({ meetingId, roomName, roomUrl, startDate, endDate, hostRoomUrl, viewerRoomUrl }) {
|
|
6935
|
-
assertString(meetingId, "meetingId");
|
|
6936
|
-
assertString(roomName, "roomName");
|
|
6937
|
-
assertString(roomUrl, "roomUrl");
|
|
6938
|
-
assertInstanceOf(startDate, Date, "startDate");
|
|
6939
|
-
assertInstanceOf(endDate, Date, "endDate");
|
|
6940
|
-
this.meetingId = meetingId;
|
|
6941
|
-
this.roomName = roomName;
|
|
6942
|
-
this.roomUrl = roomUrl;
|
|
6943
|
-
this.startDate = startDate;
|
|
6944
|
-
this.endDate = endDate;
|
|
6945
|
-
this.hostRoomUrl = hostRoomUrl;
|
|
6946
|
-
this.viewerRoomUrl = viewerRoomUrl;
|
|
6947
|
-
}
|
|
6948
|
-
static fromJson(data) {
|
|
6949
|
-
return new Meeting({
|
|
6950
|
-
meetingId: extractString(data, "meetingId"),
|
|
6951
|
-
roomName: extractString(data, "roomName"),
|
|
6952
|
-
roomUrl: extractString(data, "roomUrl"),
|
|
6953
|
-
startDate: extractDate(data, "startDate"),
|
|
6954
|
-
endDate: extractDate(data, "endDate"),
|
|
6955
|
-
hostRoomUrl: extractNullOrString(data, "hostRoomUrl"),
|
|
6956
|
-
viewerRoomUrl: extractNullOrString(data, "viewerRoomUrl"),
|
|
6957
|
-
});
|
|
6958
|
-
}
|
|
6959
|
-
}
|
|
6960
|
-
|
|
6961
|
-
// @ts-nocheck
|
|
6962
|
-
function createRoomUrl(roomName, path = "") {
|
|
6963
|
-
const encodedDisplayName = encodeURIComponent(roomName.substring(1));
|
|
6964
|
-
return `/room/${encodedDisplayName}${path}`;
|
|
6965
|
-
}
|
|
6966
|
-
/**
|
|
6967
|
-
* Service for talking to the Room related APIs
|
|
6968
|
-
*/
|
|
6969
|
-
class RoomService {
|
|
6970
|
-
/**
|
|
6971
|
-
* @param {object} organizationApiClient (required)
|
|
6972
|
-
*/
|
|
6973
|
-
constructor({ organizationApiClient }) {
|
|
6974
|
-
this._organizationApiClient = assertInstanceOf(organizationApiClient, OrganizationApiClient);
|
|
6975
|
-
}
|
|
6976
|
-
/**
|
|
6977
|
-
* Gets the list of visited rooms
|
|
6978
|
-
*
|
|
6979
|
-
* @param {Object} args
|
|
6980
|
-
* @param {Array<string>} [types=["team"]] - The type of rooms that should be fetched.
|
|
6981
|
-
* @param {Array<string>} [fields=["meeting"]] - The fields of room that should be fetched.
|
|
6982
|
-
* @returns {Promise<array>} - It will resolve with an array.
|
|
6983
|
-
*/
|
|
6984
|
-
getRooms({ types, fields = [] } = {}) {
|
|
6985
|
-
assertArray(types, "types");
|
|
6986
|
-
assertArray(fields, "fields");
|
|
6987
|
-
return this._organizationApiClient
|
|
6988
|
-
.request("/room", {
|
|
6989
|
-
method: "GET",
|
|
6990
|
-
params: { types: types.join(","), fields: fields.join(","), includeOnlyLegacyRoomType: "false" },
|
|
6991
|
-
})
|
|
6992
|
-
.then(({ data }) => data.rooms.map((room) => new Room(room)));
|
|
6993
|
-
}
|
|
6994
|
-
/**
|
|
6995
|
-
* Gets the specified room.
|
|
6996
|
-
*
|
|
6997
|
-
* Currently information is implicitly alluded to via the servers
|
|
6998
|
-
* `/room/roomName` response. This method patches the data
|
|
6999
|
-
* tempoarily, until the day it comes back from the server.
|
|
7000
|
-
*
|
|
7001
|
-
* @returns {Promise<Room>} - It will resolve with the Room.
|
|
7002
|
-
*/
|
|
7003
|
-
getRoom({ roomName, fields }) {
|
|
7004
|
-
assertRoomName(roomName);
|
|
7005
|
-
const encodedDisplayName = encodeURIComponent(roomName.substring(1));
|
|
7006
|
-
return this._organizationApiClient
|
|
7007
|
-
.request(`/rooms/${encodedDisplayName}`, {
|
|
7008
|
-
method: "GET",
|
|
7009
|
-
params: Object.assign({ includeOnlyLegacyRoomType: "false" }, (fields && { fields: fields.join(",") })),
|
|
7010
|
-
})
|
|
7011
|
-
.then(({ data }) => new Room(Object.assign({}, data, Object.assign({ roomName }, (data.meeting && { meeting: Meeting.fromJson(data.meeting) })))))
|
|
7012
|
-
.catch((response) => {
|
|
7013
|
-
if (response.status === 404) {
|
|
7014
|
-
return new Room({
|
|
7015
|
-
roomName,
|
|
7016
|
-
isClaimed: false,
|
|
7017
|
-
mode: "normal",
|
|
7018
|
-
product: {
|
|
7019
|
-
categoryName: "personal_free",
|
|
7020
|
-
},
|
|
7021
|
-
type: "personal",
|
|
7022
|
-
legacyRoomType: "free",
|
|
7023
|
-
});
|
|
7024
|
-
}
|
|
7025
|
-
if (response.status === 400 && response.data.error === "Banned room") {
|
|
7026
|
-
return new Room({ roomName, isBanned: true });
|
|
7027
|
-
}
|
|
7028
|
-
// Either server error or something else.
|
|
7029
|
-
throw new Error(response.data ? response.data.error : "Could not fetch room information");
|
|
7030
|
-
});
|
|
7031
|
-
}
|
|
7032
|
-
/**
|
|
7033
|
-
* Claims the specified room.
|
|
7034
|
-
*
|
|
7035
|
-
* @param {Object} args
|
|
7036
|
-
* @param {String} args.roomName - The roomName to claim
|
|
7037
|
-
* @param {String} args.type - The type of room to claim
|
|
7038
|
-
* @param {String} args.mode - The optional mode of room to claim
|
|
7039
|
-
* @param {[Boolean]} args.isLocked - The optional lock status of room to claim
|
|
7040
|
-
* @returns {Promise} - It will resolve with undefined.
|
|
7041
|
-
*/
|
|
7042
|
-
claimRoom({ roomName, type, mode, isLocked }) {
|
|
7043
|
-
assertRoomName(roomName);
|
|
7044
|
-
assertString(type, "type");
|
|
7045
|
-
return this._organizationApiClient
|
|
7046
|
-
.request("/room/claim", {
|
|
7047
|
-
method: "POST",
|
|
7048
|
-
data: Object.assign(Object.assign({ roomName,
|
|
7049
|
-
type }, (typeof mode === "string" && { mode })), (typeof isLocked === "boolean" && { isLocked })),
|
|
7050
|
-
})
|
|
7051
|
-
.then(() => undefined)
|
|
7052
|
-
.catch((response) => {
|
|
7053
|
-
throw new Error(response.data.error || "Failed to claim room");
|
|
7054
|
-
});
|
|
7055
|
-
}
|
|
7056
|
-
/**
|
|
7057
|
-
* Unclaims the specified room.
|
|
7058
|
-
*
|
|
7059
|
-
* @param {string} roomName - the room name to unclaim.
|
|
7060
|
-
* @returns {Promise.<undefined>} - It will resolve with undefined.
|
|
7061
|
-
*/
|
|
7062
|
-
unclaimRoom(roomName) {
|
|
7063
|
-
assertRoomName(roomName);
|
|
7064
|
-
const encodedDisplayName = encodeURIComponent(roomName.substring(1));
|
|
7065
|
-
return this._organizationApiClient
|
|
7066
|
-
.request(`/room/${encodedDisplayName}`, {
|
|
7067
|
-
method: "DELETE",
|
|
7068
|
-
})
|
|
7069
|
-
.then(() => undefined);
|
|
7070
|
-
}
|
|
7071
|
-
/**
|
|
7072
|
-
* Changes the name of the room
|
|
7073
|
-
*
|
|
7074
|
-
* @param {Object} args
|
|
7075
|
-
* @param {string} args.roomName - The name of the room to rename
|
|
7076
|
-
* @param {string} args.newRoomName - The new name
|
|
7077
|
-
* @returns {Promise<undefined>} - It will resolve if the room was renamed, reject for all other cases
|
|
7078
|
-
*/
|
|
7079
|
-
renameRoom({ roomName, newRoomName }) {
|
|
7080
|
-
assertRoomName(roomName);
|
|
7081
|
-
assertString(newRoomName, "newRoomName");
|
|
7082
|
-
const encodedRoomName = encodeURIComponent(roomName.substring(1));
|
|
7083
|
-
return this._organizationApiClient
|
|
7084
|
-
.request(`/room/${encodedRoomName}/roomName`, {
|
|
7085
|
-
method: "PUT",
|
|
7086
|
-
data: { newRoomName },
|
|
7087
|
-
})
|
|
7088
|
-
.then(() => undefined);
|
|
7089
|
-
}
|
|
7090
|
-
/**
|
|
7091
|
-
* Changes the room mode (experimental)
|
|
7092
|
-
*
|
|
7093
|
-
* @param {string} roomName - The name of the room to change mode of
|
|
7094
|
-
* @param {string} mode - The name of mode to set, currently only "group" is supported
|
|
7095
|
-
* @returns {Promise<undefined>} - It will resolve if mode was changed, rejects for all other cases
|
|
7096
|
-
*/
|
|
7097
|
-
changeMode({ roomName, mode }) {
|
|
7098
|
-
assertRoomName(roomName);
|
|
7099
|
-
assertString(mode, "mode");
|
|
7100
|
-
const encodedDisplayName = encodeURIComponent(roomName.substring(1));
|
|
7101
|
-
return this._organizationApiClient
|
|
7102
|
-
.request(`/room/${encodedDisplayName}/mode`, {
|
|
7103
|
-
method: "PUT",
|
|
7104
|
-
data: { mode },
|
|
7105
|
-
})
|
|
7106
|
-
.then(() => undefined);
|
|
7107
|
-
}
|
|
7108
|
-
/**
|
|
7109
|
-
* Updates the room prefs
|
|
7110
|
-
*
|
|
7111
|
-
* @param {string} roomName - The name of the room to change mode of
|
|
7112
|
-
* @param {object} preferences - The prefs you want to update and their values
|
|
7113
|
-
* @returns {Promise<undefined>} - It will resolve if updated, rejects for all other cases
|
|
7114
|
-
*/
|
|
7115
|
-
updatePreferences({ roomName, preferences }) {
|
|
7116
|
-
assertRoomName(roomName);
|
|
7117
|
-
assertInstanceOf(preferences, Object, "preferences");
|
|
7118
|
-
const encodedDisplayName = encodeURIComponent(roomName.substring(1));
|
|
7119
|
-
return this._organizationApiClient
|
|
7120
|
-
.request(`/room/${encodedDisplayName}/preferences`, {
|
|
7121
|
-
method: "PATCH",
|
|
7122
|
-
data: preferences,
|
|
7123
|
-
})
|
|
7124
|
-
.then(() => undefined);
|
|
7125
|
-
}
|
|
7126
|
-
/**
|
|
7127
|
-
* Updates the protected room prefs
|
|
7128
|
-
*
|
|
7129
|
-
* @param {string} roomName - The name of the room to change mode of
|
|
7130
|
-
* @param {object} preferences - The protected prefs you want to update and their values
|
|
7131
|
-
* @returns {Promise<undefined>} - It will resolve if updated, rejects for all other cases
|
|
7132
|
-
*/
|
|
7133
|
-
updateProtectedPreferences({ roomName, preferences }) {
|
|
7134
|
-
assertRoomName(roomName);
|
|
7135
|
-
assertInstanceOf(preferences, Object, "preferences");
|
|
7136
|
-
const encodedDisplayName = encodeURIComponent(roomName.substring(1));
|
|
7137
|
-
return this._organizationApiClient
|
|
7138
|
-
.request(`/room/${encodedDisplayName}/protected-preferences`, {
|
|
7139
|
-
method: "PATCH",
|
|
7140
|
-
data: preferences,
|
|
7141
|
-
})
|
|
7142
|
-
.then(() => undefined);
|
|
7143
|
-
}
|
|
7144
|
-
getRoomPermissions(roomName, { roomKey } = {}) {
|
|
7145
|
-
assertRoomName(roomName);
|
|
7146
|
-
return this._organizationApiClient
|
|
7147
|
-
.request(createRoomUrl(roomName, "/permissions"), Object.assign({ method: "GET" }, (roomKey && { headers: { "X-Whereby-Room-Key": roomKey } })))
|
|
7148
|
-
.then((response) => {
|
|
7149
|
-
const { permissions, limits } = response.data;
|
|
7150
|
-
return {
|
|
7151
|
-
permissions,
|
|
7152
|
-
limits,
|
|
7153
|
-
};
|
|
7154
|
-
});
|
|
7155
|
-
}
|
|
7156
|
-
/**
|
|
7157
|
-
* Gets the specified room metrics
|
|
7158
|
-
*
|
|
7159
|
-
* @param {Object} args
|
|
7160
|
-
* @param {string} args.roomName - The name of the room to get metrics from.
|
|
7161
|
-
* @param {string} args.metrics - Comma-separated list of metrics to include.
|
|
7162
|
-
* @param {string} args.from (optional) - Start time (inclusive) to count from in
|
|
7163
|
-
* ISO format. Defaults to counting from the start of time.
|
|
7164
|
-
* @param {string} args.to (optional) - End time (exclusive) to count up to in
|
|
7165
|
-
* ISO format. Defaults to counting up to the current time.
|
|
7166
|
-
* @returns {Promise<Object>} - It will resolve with the requested metrics.
|
|
7167
|
-
*/
|
|
7168
|
-
getRoomMetrics({ roomName, metrics, from, to }) {
|
|
7169
|
-
assertRoomName(roomName);
|
|
7170
|
-
assertString(metrics, "metrics");
|
|
7171
|
-
return this._organizationApiClient
|
|
7172
|
-
.request(createRoomUrl(roomName, "/metrics"), {
|
|
7173
|
-
method: "GET",
|
|
7174
|
-
params: { metrics, from, to },
|
|
7175
|
-
})
|
|
7176
|
-
.then((response) => response.data);
|
|
7177
|
-
}
|
|
7178
|
-
/**
|
|
7179
|
-
* Changes the room type
|
|
7180
|
-
*
|
|
7181
|
-
* @param {Object} args
|
|
7182
|
-
* @param {string} args.roomName - The name of the room to change mode of
|
|
7183
|
-
* @param {"personal" | "personal_xl"} args.type - Room type that should be set
|
|
7184
|
-
* @returns {Promise<undefined>} - It will resolve if type was changed, rejects for all other cases
|
|
7185
|
-
*/
|
|
7186
|
-
changeType({ roomName, type }) {
|
|
7187
|
-
assertRoomName(roomName);
|
|
7188
|
-
assertOneOf(type, ["personal", "personal_xl"], "type");
|
|
7189
|
-
const encodedDisplayName = encodeURIComponent(roomName.substring(1));
|
|
7190
|
-
return this._organizationApiClient
|
|
7191
|
-
.request(`/room/${encodedDisplayName}/type`, {
|
|
7192
|
-
method: "PUT",
|
|
7193
|
-
data: { type },
|
|
7194
|
-
})
|
|
7195
|
-
.then(() => undefined);
|
|
7196
|
-
}
|
|
7197
|
-
/** Gets a Forest campaign social image
|
|
7198
|
-
*
|
|
7199
|
-
* @param {Object} args
|
|
7200
|
-
* @param {string} args.roomName - The name of the room to get metrics from.
|
|
7201
|
-
* @param {number} args.count - Number to be displayed in the image as tree count.
|
|
7202
|
-
* @returns {Promise<string>} - It will resolve with the image url.
|
|
7203
|
-
*/
|
|
7204
|
-
getForestSocialImage({ roomName, count }) {
|
|
7205
|
-
assertRoomName(roomName);
|
|
7206
|
-
assertNumber(count, "count");
|
|
7207
|
-
return this._organizationApiClient
|
|
7208
|
-
.request(createRoomUrl(roomName, `/forest-social-image/${count}`), {
|
|
7209
|
-
method: "GET",
|
|
7210
|
-
})
|
|
7211
|
-
.then((response) => response.data.imageUrl);
|
|
7212
|
-
}
|
|
7213
|
-
}
|
|
7214
|
-
|
|
7215
|
-
class RoomParticipant {
|
|
7216
|
-
constructor({ displayName, id, stream, isAudioEnabled, isVideoEnabled }) {
|
|
7217
|
-
this.isLocalParticipant = false;
|
|
7218
|
-
this.displayName = displayName;
|
|
7219
|
-
this.id = id;
|
|
7220
|
-
this.stream = stream;
|
|
7221
|
-
this.isAudioEnabled = isAudioEnabled;
|
|
7222
|
-
this.isVideoEnabled = isVideoEnabled;
|
|
7223
|
-
}
|
|
7224
|
-
}
|
|
7225
|
-
class RemoteParticipant extends RoomParticipant {
|
|
7226
|
-
constructor({ displayName, id, newJoiner, streams, isAudioEnabled, isVideoEnabled, }) {
|
|
7227
|
-
super({ displayName, id, isAudioEnabled, isVideoEnabled });
|
|
7228
|
-
this.newJoiner = newJoiner;
|
|
7229
|
-
this.streams = streams.map((streamId) => ({ id: streamId, state: newJoiner ? "new_accept" : "to_accept" }));
|
|
7230
|
-
}
|
|
7231
|
-
addStream(streamId, state) {
|
|
7232
|
-
this.streams.push({ id: streamId, state });
|
|
7233
|
-
}
|
|
7234
|
-
removeStream(streamId) {
|
|
7235
|
-
const index = this.streams.findIndex((s) => s.id === streamId);
|
|
7236
|
-
if (index !== -1) {
|
|
7237
|
-
this.streams.splice(index, 1);
|
|
7238
|
-
}
|
|
7239
|
-
}
|
|
7240
|
-
updateStreamState(streamId, state) {
|
|
7241
|
-
const stream = this.streams.find((s) => s.id === streamId);
|
|
7242
|
-
if (stream) {
|
|
7243
|
-
stream.state = state;
|
|
7244
|
-
}
|
|
7245
|
-
}
|
|
7246
|
-
}
|
|
7247
|
-
class LocalParticipant extends RoomParticipant {
|
|
7248
|
-
constructor({ displayName, id, stream, isAudioEnabled, isVideoEnabled }) {
|
|
7249
|
-
super({ displayName, id, stream, isAudioEnabled, isVideoEnabled });
|
|
7250
|
-
this.isLocalParticipant = true;
|
|
7251
|
-
}
|
|
7252
|
-
}
|
|
7253
|
-
|
|
7254
|
-
const sdkVersion = "2.0.0-beta3";
|
|
7255
|
-
|
|
7256
|
-
class RoomConnectionEvent extends CustomEvent {
|
|
7257
|
-
constructor(eventType, eventInitDict) {
|
|
7258
|
-
super(eventType, eventInitDict);
|
|
7259
|
-
}
|
|
7260
|
-
}
|
|
7261
|
-
const API_BASE_URL = "https://api.whereby.dev" ;
|
|
7262
|
-
const SIGNAL_BASE_URL = "wss://signal.appearin.net" ;
|
|
7263
|
-
const NON_PERSON_ROLES = ["recorder", "streamer"];
|
|
7264
|
-
// cache last reported stream resolutions
|
|
7265
|
-
const reportedStreamResolutions = new Map();
|
|
7266
|
-
function createSocket() {
|
|
7267
|
-
const parsedUrl = new URL(SIGNAL_BASE_URL);
|
|
7268
|
-
const socketHost = parsedUrl.origin;
|
|
7269
|
-
const socketOverrides = {
|
|
7270
|
-
autoConnect: false,
|
|
7271
|
-
};
|
|
7272
|
-
return new ServerSocket(socketHost, socketOverrides);
|
|
7273
|
-
}
|
|
7274
|
-
function handleStreamAdded(remoteParticipants, { clientId, stream, streamId, streamType }) {
|
|
7275
|
-
if (!streamId) {
|
|
7276
|
-
streamId = stream.id;
|
|
7277
|
-
}
|
|
7278
|
-
const remoteParticipant = remoteParticipants.find((p) => p.id === clientId);
|
|
7279
|
-
if (!remoteParticipant) {
|
|
7280
|
-
return;
|
|
7281
|
-
}
|
|
7282
|
-
const remoteParticipantStream = remoteParticipant.streams.find((s) => s.id === streamId) || remoteParticipant.streams[0];
|
|
7283
|
-
if ((remoteParticipant.stream && remoteParticipant.stream.id === streamId) ||
|
|
7284
|
-
(!remoteParticipant.stream && streamType === "webcam") ||
|
|
7285
|
-
(!remoteParticipant.stream && !streamType && remoteParticipant.streams.indexOf(remoteParticipantStream) < 1)) {
|
|
7286
|
-
return new RoomConnectionEvent("participant_stream_added", {
|
|
7287
|
-
detail: { participantId: clientId, stream, streamId },
|
|
7288
|
-
});
|
|
7289
|
-
}
|
|
7290
|
-
// screenshare
|
|
7291
|
-
return new RoomConnectionEvent("screenshare_started", {
|
|
7292
|
-
detail: {
|
|
7293
|
-
participantId: clientId,
|
|
7294
|
-
stream,
|
|
7295
|
-
id: streamId,
|
|
7296
|
-
isLocal: false,
|
|
7297
|
-
hasAudioTrack: stream.getAudioTracks().length > 0,
|
|
7298
|
-
},
|
|
7299
|
-
});
|
|
7300
|
-
}
|
|
7301
|
-
const noop = () => {
|
|
7302
|
-
return;
|
|
7303
|
-
};
|
|
7304
|
-
const TypedEventTarget = EventTarget;
|
|
7305
|
-
class RoomConnection extends TypedEventTarget {
|
|
7306
|
-
constructor(roomUrl, { displayName, localMedia, localMediaOptions: localMediaConstraints, logger, roomKey, externalId, }) {
|
|
7307
|
-
super();
|
|
7308
|
-
this.remoteParticipants = [];
|
|
7309
|
-
this.screenshares = [];
|
|
7310
|
-
this._deviceCredentials = null;
|
|
7311
|
-
this._ownsLocalMedia = false;
|
|
7312
|
-
this.organizationId = "";
|
|
7313
|
-
this.connectionStatus = "initializing";
|
|
7314
|
-
this.selfId = null;
|
|
7315
|
-
this.roomUrl = new URL(roomUrl); // Throw if invalid Whereby room url
|
|
7316
|
-
const searchParams = new URLSearchParams(this.roomUrl.search);
|
|
7317
|
-
this._roomKey = roomKey || searchParams.get("roomKey");
|
|
7318
|
-
this.roomName = this.roomUrl.pathname;
|
|
7319
|
-
this.logger = logger || {
|
|
7320
|
-
debug: noop,
|
|
7321
|
-
error: noop,
|
|
7322
|
-
info: noop,
|
|
7323
|
-
log: noop,
|
|
7324
|
-
warn: noop,
|
|
7325
|
-
};
|
|
7326
|
-
this.displayName = displayName;
|
|
7327
|
-
this.externalId = externalId;
|
|
7328
|
-
this.localMediaConstraints = localMediaConstraints;
|
|
7329
|
-
const urls = fromLocation({ host: this.roomUrl.host });
|
|
7330
|
-
// Set up local media
|
|
7331
|
-
if (localMedia) {
|
|
7332
|
-
this.localMedia = localMedia;
|
|
7333
|
-
}
|
|
7334
|
-
else if (localMediaConstraints) {
|
|
7335
|
-
this.localMedia = new LocalMedia(localMediaConstraints);
|
|
7336
|
-
this._ownsLocalMedia = true;
|
|
7337
|
-
}
|
|
7338
|
-
else {
|
|
7339
|
-
throw new Error("Missing constraints");
|
|
7340
|
-
}
|
|
7341
|
-
// Set up services
|
|
7342
|
-
this.credentialsService = CredentialsService.create({ baseUrl: API_BASE_URL });
|
|
7343
|
-
this.apiClient = new ApiClient({
|
|
7344
|
-
fetchDeviceCredentials: this.credentialsService.getCredentials.bind(this.credentialsService),
|
|
7345
|
-
baseUrl: API_BASE_URL,
|
|
7346
|
-
});
|
|
7347
|
-
this.organizationService = new OrganizationService({ apiClient: this.apiClient });
|
|
7348
|
-
this.organizationServiceCache = new OrganizationServiceCache({
|
|
7349
|
-
organizationService: this.organizationService,
|
|
7350
|
-
subdomain: urls.subdomain,
|
|
7351
|
-
});
|
|
7352
|
-
this.organizationApiClient = new OrganizationApiClient({
|
|
7353
|
-
apiClient: this.apiClient,
|
|
7354
|
-
fetchOrganization: () => __awaiter(this, void 0, void 0, function* () {
|
|
7355
|
-
const organization = yield this.organizationServiceCache.fetchOrganization();
|
|
7356
|
-
return organization || undefined;
|
|
7357
|
-
}),
|
|
7358
|
-
});
|
|
7359
|
-
this.roomService = new RoomService({ organizationApiClient: this.organizationApiClient });
|
|
7360
|
-
// Create signal socket and set up event listeners
|
|
7361
|
-
this.signalSocket = createSocket();
|
|
7362
|
-
this.signalSocket.on("new_client", this._handleNewClient.bind(this));
|
|
7363
|
-
this.signalSocket.on("chat_message", this._handleNewChatMessage.bind(this));
|
|
7364
|
-
this.signalSocket.on("client_left", this._handleClientLeft.bind(this));
|
|
7365
|
-
this.signalSocket.on("audio_enabled", this._handleClientAudioEnabled.bind(this));
|
|
7366
|
-
this.signalSocket.on("video_enabled", this._handleClientVideoEnabled.bind(this));
|
|
7367
|
-
this.signalSocket.on("client_metadata_received", this._handleClientMetadataReceived.bind(this));
|
|
7368
|
-
this.signalSocket.on("knock_handled", this._handleKnockHandled.bind(this));
|
|
7369
|
-
this.signalSocket.on("knocker_left", this._handleKnockerLeft.bind(this));
|
|
7370
|
-
this.signalSocket.on("room_joined", this._handleRoomJoined.bind(this));
|
|
7371
|
-
this.signalSocket.on("room_knocked", this._handleRoomKnocked.bind(this));
|
|
7372
|
-
this.signalSocket.on("cloud_recording_started", this._handleCloudRecordingStarted.bind(this));
|
|
7373
|
-
this.signalSocket.on("cloud_recording_stopped", this._handleCloudRecordingStopped.bind(this));
|
|
7374
|
-
this.signalSocket.on("screenshare_started", this._handleScreenshareStarted.bind(this));
|
|
7375
|
-
this.signalSocket.on("screenshare_stopped", this._handleScreenshareStopped.bind(this));
|
|
7376
|
-
this.signalSocket.on("streaming_stopped", this._handleStreamingStopped.bind(this));
|
|
7377
|
-
this.signalSocket.on("disconnect", this._handleDisconnect.bind(this));
|
|
7378
|
-
this.signalSocket.on("connect_error", this._handleDisconnect.bind(this));
|
|
7379
|
-
this.signalSocketManager = this.signalSocket.getManager();
|
|
7380
|
-
this.signalSocketManager.on("reconnect", this._handleReconnect.bind(this));
|
|
7381
|
-
// Set up local media listeners
|
|
7382
|
-
this.localMedia.addEventListener("camera_enabled", (e) => {
|
|
7383
|
-
const { enabled } = e.detail;
|
|
7384
|
-
this.signalSocket.emit("enable_video", { enabled });
|
|
7385
|
-
this.dispatchEvent(new RoomConnectionEvent("local_camera_enabled", { detail: { enabled } }));
|
|
7386
|
-
});
|
|
7387
|
-
this.localMedia.addEventListener("microphone_enabled", (e) => {
|
|
7388
|
-
const { enabled } = e.detail;
|
|
7389
|
-
this.signalSocket.emit("enable_audio", { enabled });
|
|
7390
|
-
this.dispatchEvent(new RoomConnectionEvent("local_microphone_enabled", { detail: { enabled } }));
|
|
7391
|
-
});
|
|
7392
|
-
const webrtcProvider = {
|
|
7393
|
-
getMediaConstraints: () => ({
|
|
7394
|
-
audio: this.localMedia.isMicrophoneEnabled(),
|
|
7395
|
-
video: this.localMedia.isCameraEnabled(),
|
|
7396
|
-
}),
|
|
7397
|
-
deferrable(clientId) {
|
|
7398
|
-
return !clientId;
|
|
7399
|
-
},
|
|
7400
|
-
};
|
|
7401
|
-
this.rtcManagerDispatcher = new RtcManagerDispatcher({
|
|
7402
|
-
emitter: {
|
|
7403
|
-
emit: this._handleRtcEvent.bind(this),
|
|
7404
|
-
},
|
|
7405
|
-
serverSocket: this.signalSocket,
|
|
7406
|
-
webrtcProvider,
|
|
7407
|
-
features: {
|
|
7408
|
-
lowDataModeEnabled: false,
|
|
7409
|
-
sfuServerOverrideHost: undefined,
|
|
7410
|
-
turnServerOverrideHost: undefined,
|
|
7411
|
-
useOnlyTURN: undefined,
|
|
7412
|
-
vp9On: false,
|
|
7413
|
-
h264On: false,
|
|
7414
|
-
simulcastScreenshareOn: false,
|
|
7415
|
-
},
|
|
7416
|
-
logger: this.logger,
|
|
7417
|
-
});
|
|
7418
|
-
}
|
|
7419
|
-
get roomKey() {
|
|
7420
|
-
return this._roomKey;
|
|
7421
|
-
}
|
|
7422
|
-
_handleNewChatMessage(message) {
|
|
7423
|
-
this.dispatchEvent(new RoomConnectionEvent("chat_message", { detail: message }));
|
|
7424
|
-
}
|
|
7425
|
-
_handleCloudRecordingStarted(event) {
|
|
7426
|
-
// Only handle the start failure event here. The recording is
|
|
7427
|
-
// considered started when the recorder client joins.
|
|
7428
|
-
if (event.error) {
|
|
7429
|
-
this.dispatchEvent(new RoomConnectionEvent("cloud_recording_started_error", {
|
|
7430
|
-
detail: { error: event.error, status: "error" },
|
|
7431
|
-
}));
|
|
7432
|
-
}
|
|
7433
|
-
}
|
|
7434
|
-
_handleRecorderClientJoined({ client }) {
|
|
7435
|
-
this.dispatchEvent(new RoomConnectionEvent("cloud_recording_started", {
|
|
7436
|
-
detail: {
|
|
7437
|
-
status: "recording",
|
|
7438
|
-
startedAt: client.startedCloudRecordingAt
|
|
7439
|
-
? new Date(client.startedCloudRecordingAt).getTime()
|
|
7440
|
-
: new Date().getTime(),
|
|
7441
|
-
},
|
|
7442
|
-
}));
|
|
7443
|
-
}
|
|
7444
|
-
_handleStreamingStarted() {
|
|
7445
|
-
this.dispatchEvent(new RoomConnectionEvent("streaming_started", {
|
|
7446
|
-
detail: {
|
|
7447
|
-
status: "streaming",
|
|
7448
|
-
// We don't have the streaming start time stored on the
|
|
7449
|
-
// server, so we use the current time instead. This gives
|
|
7450
|
-
// an invalid timestamp for "Client B" if "Client A" has
|
|
7451
|
-
// been streaming for a while before "Client B" joins.
|
|
7452
|
-
startedAt: new Date().getTime(),
|
|
7453
|
-
},
|
|
7454
|
-
}));
|
|
7455
|
-
}
|
|
7456
|
-
_handleNewClient({ client }) {
|
|
7457
|
-
if (client.role.roleName === "recorder") {
|
|
7458
|
-
this._handleRecorderClientJoined({ client });
|
|
7459
|
-
}
|
|
7460
|
-
if (client.role.roleName === "streamer") {
|
|
7461
|
-
this._handleStreamingStarted();
|
|
7462
|
-
}
|
|
7463
|
-
if (NON_PERSON_ROLES.includes(client.role.roleName)) {
|
|
7464
|
-
return;
|
|
7465
|
-
}
|
|
7466
|
-
const remoteParticipant = new RemoteParticipant(Object.assign(Object.assign({}, client), { newJoiner: true }));
|
|
7467
|
-
this.remoteParticipants = [...this.remoteParticipants, remoteParticipant];
|
|
7468
|
-
this._handleAcceptStreams([remoteParticipant]);
|
|
7469
|
-
this.dispatchEvent(new RoomConnectionEvent("participant_joined", {
|
|
7470
|
-
detail: { remoteParticipant },
|
|
7471
|
-
}));
|
|
7472
|
-
}
|
|
7473
|
-
_handleClientLeft({ clientId }) {
|
|
7474
|
-
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
7475
|
-
this.remoteParticipants = this.remoteParticipants.filter((p) => p.id !== clientId);
|
|
7476
|
-
if (!remoteParticipant) {
|
|
7477
|
-
return;
|
|
7478
|
-
}
|
|
7479
|
-
this.dispatchEvent(new RoomConnectionEvent("participant_left", { detail: { participantId: remoteParticipant.id } }));
|
|
7480
|
-
}
|
|
7481
|
-
_handleClientAudioEnabled({ clientId, isAudioEnabled }) {
|
|
7482
|
-
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
7483
|
-
if (!remoteParticipant) {
|
|
7484
|
-
return;
|
|
7485
|
-
}
|
|
7486
|
-
this.dispatchEvent(new RoomConnectionEvent("participant_audio_enabled", {
|
|
7487
|
-
detail: { participantId: remoteParticipant.id, isAudioEnabled },
|
|
7488
|
-
}));
|
|
7489
|
-
}
|
|
7490
|
-
_handleClientVideoEnabled({ clientId, isVideoEnabled }) {
|
|
7491
|
-
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
7492
|
-
if (!remoteParticipant) {
|
|
7493
|
-
return;
|
|
7494
|
-
}
|
|
7495
|
-
this.dispatchEvent(new RoomConnectionEvent("participant_video_enabled", {
|
|
7496
|
-
detail: { participantId: remoteParticipant.id, isVideoEnabled },
|
|
7497
|
-
}));
|
|
7498
|
-
}
|
|
7499
|
-
_handleClientMetadataReceived({ payload: { clientId, displayName } }) {
|
|
7500
|
-
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
7501
|
-
if (!remoteParticipant) {
|
|
7502
|
-
return;
|
|
7503
|
-
}
|
|
7504
|
-
this.dispatchEvent(new RoomConnectionEvent("participant_metadata_changed", {
|
|
7505
|
-
detail: { participantId: remoteParticipant.id, displayName },
|
|
7506
|
-
}));
|
|
7507
|
-
}
|
|
7508
|
-
_handleKnockHandled(payload) {
|
|
7509
|
-
const { clientId, resolution } = payload;
|
|
7510
|
-
// If the knocker is not the local participant, ignore the event
|
|
7511
|
-
if (clientId !== this.selfId) {
|
|
7512
|
-
return;
|
|
7513
|
-
}
|
|
7514
|
-
if (resolution === "accepted") {
|
|
7515
|
-
this._roomKey = payload.metadata.roomKey;
|
|
7516
|
-
this._joinRoom();
|
|
7517
|
-
}
|
|
7518
|
-
else if (resolution === "rejected") {
|
|
7519
|
-
this.connectionStatus = "knock_rejected";
|
|
7520
|
-
this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
|
|
7521
|
-
detail: {
|
|
7522
|
-
connectionStatus: this.connectionStatus,
|
|
7523
|
-
},
|
|
7524
|
-
}));
|
|
7525
|
-
}
|
|
7526
|
-
}
|
|
7527
|
-
_handleKnockerLeft(payload) {
|
|
7528
|
-
const { clientId } = payload;
|
|
7529
|
-
this.dispatchEvent(new RoomConnectionEvent("waiting_participant_left", {
|
|
7530
|
-
detail: { participantId: clientId },
|
|
7531
|
-
}));
|
|
7532
|
-
}
|
|
7533
|
-
_handleRoomJoined(event) {
|
|
7534
|
-
const { error, isLocked, room, selfId } = event;
|
|
7535
|
-
this.selfId = selfId;
|
|
7536
|
-
if (error === "room_locked" && isLocked) {
|
|
7537
|
-
this.connectionStatus = "room_locked";
|
|
7538
|
-
this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
|
|
7539
|
-
detail: {
|
|
7540
|
-
connectionStatus: this.connectionStatus,
|
|
7541
|
-
},
|
|
7542
|
-
}));
|
|
7543
|
-
return;
|
|
7544
|
-
}
|
|
7545
|
-
// Check if we have an error
|
|
7546
|
-
// Check if it is a room joined error
|
|
7547
|
-
// Set state to connect_failed_locked
|
|
7548
|
-
// Set state to connect_failed_no_host
|
|
7549
|
-
if (room) {
|
|
7550
|
-
const { clients, knockers } = room;
|
|
7551
|
-
const localClient = clients.find((c) => c.id === selfId);
|
|
7552
|
-
if (!localClient)
|
|
7553
|
-
throw new Error("Missing local client");
|
|
7554
|
-
this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localMedia.stream || undefined }));
|
|
7555
|
-
const recorderClient = clients.find((c) => c.role.roleName === "recorder");
|
|
7556
|
-
if (recorderClient) {
|
|
7557
|
-
this._handleRecorderClientJoined({ client: recorderClient });
|
|
7558
|
-
}
|
|
7559
|
-
const streamerClient = clients.find((c) => c.role.roleName === "streamer");
|
|
7560
|
-
if (streamerClient) {
|
|
7561
|
-
this._handleStreamingStarted();
|
|
7562
|
-
}
|
|
7563
|
-
this.remoteParticipants = clients
|
|
7564
|
-
.filter((c) => c.id !== selfId)
|
|
7565
|
-
.filter((c) => !NON_PERSON_ROLES.includes(c.role.roleName))
|
|
7566
|
-
.map((c) => new RemoteParticipant(Object.assign(Object.assign({}, c), { newJoiner: false })));
|
|
7567
|
-
this.connectionStatus = "connected";
|
|
7568
|
-
this.dispatchEvent(new RoomConnectionEvent("room_joined", {
|
|
7569
|
-
detail: {
|
|
7570
|
-
localParticipant: this.localParticipant,
|
|
7571
|
-
remoteParticipants: this.remoteParticipants,
|
|
7572
|
-
waitingParticipants: knockers.map((knocker) => {
|
|
7573
|
-
return { id: knocker.clientId, displayName: knocker.displayName };
|
|
7574
|
-
}),
|
|
7575
|
-
},
|
|
7576
|
-
}));
|
|
7577
|
-
}
|
|
7578
|
-
}
|
|
7579
|
-
_handleRoomKnocked(event) {
|
|
7580
|
-
const { clientId, displayName } = event;
|
|
7581
|
-
this.dispatchEvent(new RoomConnectionEvent("waiting_participant_joined", {
|
|
7582
|
-
detail: { participantId: clientId, displayName },
|
|
7583
|
-
}));
|
|
7584
|
-
}
|
|
7585
|
-
_handleReconnect() {
|
|
7586
|
-
this.logger.log("Reconnected to signal socket");
|
|
7587
|
-
this.signalSocket.emit("identify_device", { deviceCredentials: this._deviceCredentials });
|
|
7588
|
-
this.signalSocket.once("device_identified", () => {
|
|
7589
|
-
this._joinRoom();
|
|
7590
|
-
});
|
|
7591
|
-
}
|
|
7592
|
-
_handleDisconnect() {
|
|
7593
|
-
this.connectionStatus = "disconnected";
|
|
7594
|
-
this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
|
|
7595
|
-
detail: {
|
|
7596
|
-
connectionStatus: this.connectionStatus,
|
|
7597
|
-
},
|
|
7598
|
-
}));
|
|
7599
|
-
}
|
|
7600
|
-
_handleCloudRecordingStopped() {
|
|
7601
|
-
this.dispatchEvent(new RoomConnectionEvent("cloud_recording_stopped"));
|
|
7602
|
-
}
|
|
7603
|
-
_handleStreamingStopped() {
|
|
7604
|
-
this.dispatchEvent(new RoomConnectionEvent("streaming_stopped"));
|
|
7605
|
-
}
|
|
7606
|
-
_handleScreenshareStarted(screenshare) {
|
|
7607
|
-
const { clientId: participantId, streamId: id, hasAudioTrack } = screenshare;
|
|
7608
|
-
const remoteParticipant = this.remoteParticipants.find((p) => p.id === participantId);
|
|
7609
|
-
if (!remoteParticipant) {
|
|
7610
|
-
this.logger.log("WARN: Could not find participant for screenshare");
|
|
7611
|
-
return;
|
|
7612
|
-
}
|
|
7613
|
-
const foundScreenshare = this.screenshares.find((s) => s.id === id);
|
|
7614
|
-
if (foundScreenshare) {
|
|
7615
|
-
this.logger.log("WARN: Screenshare already exists");
|
|
7616
|
-
return;
|
|
7617
|
-
}
|
|
7618
|
-
remoteParticipant.addStream(id, "to_accept");
|
|
7619
|
-
this._handleAcceptStreams([remoteParticipant]);
|
|
7620
|
-
this.screenshares = [
|
|
7621
|
-
...this.screenshares,
|
|
7622
|
-
{ participantId, id, hasAudioTrack, stream: undefined, isLocal: false },
|
|
7623
|
-
];
|
|
7624
|
-
}
|
|
7625
|
-
_handleScreenshareStopped(screenshare) {
|
|
7626
|
-
const { clientId: participantId, streamId: id } = screenshare;
|
|
7627
|
-
const remoteParticipant = this.remoteParticipants.find((p) => p.id === participantId);
|
|
7628
|
-
if (!remoteParticipant) {
|
|
7629
|
-
this.logger.log("WARN: Could not find participant for screenshare");
|
|
7630
|
-
return;
|
|
7631
|
-
}
|
|
7632
|
-
remoteParticipant.removeStream(id);
|
|
7633
|
-
this.screenshares = this.screenshares.filter((s) => !(s.participantId === participantId && s.id === id));
|
|
7634
|
-
this.dispatchEvent(new RoomConnectionEvent("screenshare_stopped", { detail: { participantId, id } }));
|
|
7635
|
-
}
|
|
7636
|
-
_handleRtcEvent(eventName, data) {
|
|
7637
|
-
if (eventName === "rtc_manager_created") {
|
|
7638
|
-
return this._handleRtcManagerCreated(data);
|
|
7639
|
-
}
|
|
7640
|
-
else if (eventName === "stream_added") {
|
|
7641
|
-
return this._handleStreamAdded(data);
|
|
7642
|
-
}
|
|
7643
|
-
else if (eventName === "rtc_manager_destroyed") {
|
|
7644
|
-
return this._handleRtcManagerDestroyed();
|
|
7645
|
-
}
|
|
7646
|
-
else {
|
|
7647
|
-
this.logger.log(`Unhandled RTC event ${eventName}`);
|
|
7648
|
-
}
|
|
7649
|
-
}
|
|
7650
|
-
_handleRtcManagerCreated({ rtcManager }) {
|
|
7651
|
-
var _a;
|
|
7652
|
-
this.rtcManager = rtcManager;
|
|
7653
|
-
this.localMedia.addRtcManager(rtcManager);
|
|
7654
|
-
if (this.localMedia.stream) {
|
|
7655
|
-
(_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.addNewStream("0", this.localMedia.stream, !this.localMedia.isMicrophoneEnabled(), !this.localMedia.isCameraEnabled());
|
|
7656
|
-
}
|
|
7657
|
-
if (this.remoteParticipants.length) {
|
|
7658
|
-
this._handleAcceptStreams(this.remoteParticipants);
|
|
7659
|
-
}
|
|
7660
|
-
}
|
|
7661
|
-
_handleRtcManagerDestroyed() {
|
|
7662
|
-
this.rtcManager = undefined;
|
|
7663
|
-
}
|
|
7664
|
-
_handleAcceptStreams(remoteParticipants) {
|
|
7665
|
-
var _a, _b;
|
|
7666
|
-
if (!this.rtcManager) {
|
|
7667
|
-
this.logger.log("Unable to accept streams, no rtc manager");
|
|
7668
|
-
return;
|
|
7669
|
-
}
|
|
7670
|
-
const shouldAcceptNewClients = (_b = (_a = this.rtcManager).shouldAcceptStreamsFromBothSides) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
7671
|
-
const activeBreakout = false; // TODO: Remove this once breakout is implemented
|
|
7672
|
-
remoteParticipants.forEach((participant) => {
|
|
7673
|
-
const { id: participantId, streams, newJoiner } = participant;
|
|
7674
|
-
streams.forEach((stream) => {
|
|
7675
|
-
var _a, _b;
|
|
7676
|
-
const { id: streamId, state: streamState } = stream;
|
|
7677
|
-
let newState = undefined;
|
|
7678
|
-
{
|
|
7679
|
-
if (streamState !== "done_accept") {
|
|
7680
|
-
newState = `${newJoiner && streamId === "0" ? "new" : "to"}_accept`;
|
|
7681
|
-
}
|
|
7682
|
-
}
|
|
7683
|
-
if (!newState) {
|
|
7684
|
-
return;
|
|
7685
|
-
}
|
|
7686
|
-
if (newState === "to_accept" ||
|
|
7687
|
-
(newState === "new_accept" && shouldAcceptNewClients) ||
|
|
7688
|
-
(newState === "old_accept" && !shouldAcceptNewClients)) {
|
|
7689
|
-
this.logger.log(`Accepting stream ${streamId} from ${participantId}`);
|
|
7690
|
-
(_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.acceptNewStream({
|
|
7691
|
-
streamId: streamId === "0" ? participantId : streamId,
|
|
7692
|
-
clientId: participantId,
|
|
7693
|
-
shouldAddLocalVideo: streamId === "0",
|
|
7694
|
-
activeBreakout,
|
|
7695
|
-
});
|
|
7696
|
-
}
|
|
7697
|
-
else if (newState === "new_accept" || newState === "old_accept") ;
|
|
7698
|
-
else if (newState === "to_unaccept") {
|
|
7699
|
-
this.logger.log(`Disconnecting stream ${streamId} from ${participantId}`);
|
|
7700
|
-
(_b = this.rtcManager) === null || _b === void 0 ? void 0 : _b.disconnect(streamId === "0" ? participantId : streamId, activeBreakout);
|
|
7701
|
-
}
|
|
7702
|
-
else if (newState !== "done_accept") {
|
|
7703
|
-
this.logger.warn(`Stream state not handled: ${newState} for ${participantId}-${streamId}`);
|
|
7704
|
-
return;
|
|
8477
|
+
return Organization.fromJson(data);
|
|
8478
|
+
})
|
|
8479
|
+
.catch((res) => {
|
|
8480
|
+
if (res instanceof Response) {
|
|
8481
|
+
if (res.status === 404) {
|
|
8482
|
+
return null;
|
|
7705
8483
|
}
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
});
|
|
8484
|
+
throw new Error(res.statusText);
|
|
8485
|
+
}
|
|
8486
|
+
throw res;
|
|
7710
8487
|
});
|
|
7711
8488
|
}
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
8489
|
+
/**
|
|
8490
|
+
* Retrieves the organizations that contain a user
|
|
8491
|
+
* matching provided the email+code or phoneNumber+code
|
|
8492
|
+
* combination.
|
|
8493
|
+
*/
|
|
8494
|
+
getOrganizationsByContactPoint(options) {
|
|
8495
|
+
const { code } = options;
|
|
8496
|
+
const email = "email" in options ? options.email : null;
|
|
8497
|
+
const phoneNumber = "phoneNumber" in options ? options.phoneNumber : null;
|
|
8498
|
+
assert$1.ok((email || phoneNumber) && !(email && phoneNumber), "either email or phoneNumber is required");
|
|
8499
|
+
assertString(code, "code");
|
|
8500
|
+
const contactPoint = email ? { type: "email", value: email } : { type: "phoneNumber", value: phoneNumber };
|
|
8501
|
+
return this._apiClient
|
|
8502
|
+
.request("/organization-queries", {
|
|
8503
|
+
method: "POST",
|
|
8504
|
+
data: {
|
|
8505
|
+
contactPoint,
|
|
8506
|
+
code,
|
|
7724
8507
|
},
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
isDevicePermissionDenied: false,
|
|
7729
|
-
kickFromOtherRooms: false,
|
|
7730
|
-
organizationId: this.organizationId,
|
|
7731
|
-
roomKey: this.roomKey,
|
|
7732
|
-
roomName: this.roomName,
|
|
7733
|
-
selfId: "",
|
|
7734
|
-
userAgent: `browser-sdk:${sdkVersion }`,
|
|
7735
|
-
externalId: this.externalId,
|
|
7736
|
-
});
|
|
7737
|
-
}
|
|
7738
|
-
join() {
|
|
7739
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
7740
|
-
if (["connected", "connecting"].includes(this.connectionStatus)) {
|
|
7741
|
-
console.warn(`Trying to join when room state is already ${this.connectionStatus}`);
|
|
7742
|
-
return;
|
|
7743
|
-
}
|
|
7744
|
-
this.signalSocket.connect();
|
|
7745
|
-
this.connectionStatus = "connecting";
|
|
7746
|
-
this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
|
|
7747
|
-
detail: {
|
|
7748
|
-
connectionStatus: this.connectionStatus,
|
|
7749
|
-
},
|
|
7750
|
-
}));
|
|
7751
|
-
const organization = yield this.organizationServiceCache.fetchOrganization();
|
|
7752
|
-
if (!organization) {
|
|
7753
|
-
throw new Error("Invalid room url");
|
|
7754
|
-
}
|
|
7755
|
-
this.organizationId = organization.organizationId;
|
|
7756
|
-
if (this._ownsLocalMedia) {
|
|
7757
|
-
yield this.localMedia.start();
|
|
7758
|
-
}
|
|
7759
|
-
// Identify device on signal connection
|
|
7760
|
-
this._deviceCredentials = yield this.credentialsService.getCredentials();
|
|
7761
|
-
this.logger.log("Connected to signal socket");
|
|
7762
|
-
this.signalSocket.emit("identify_device", { deviceCredentials: this._deviceCredentials });
|
|
7763
|
-
this.signalSocket.once("device_identified", () => {
|
|
7764
|
-
this._joinRoom();
|
|
7765
|
-
});
|
|
8508
|
+
})
|
|
8509
|
+
.then(({ data }) => {
|
|
8510
|
+
return extractArray(data, "organizations", (organization) => Organization.fromJson(organization));
|
|
7766
8511
|
});
|
|
7767
8512
|
}
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
8513
|
+
/**
|
|
8514
|
+
* Retrieves the organizations that contain a user
|
|
8515
|
+
* matching provided the idToken
|
|
8516
|
+
*/
|
|
8517
|
+
getOrganizationsByIdToken({ idToken }) {
|
|
8518
|
+
assertString(idToken, "idToken");
|
|
8519
|
+
return this._apiClient
|
|
8520
|
+
.request("/organization-queries", {
|
|
8521
|
+
method: "POST",
|
|
8522
|
+
data: {
|
|
8523
|
+
idToken,
|
|
7773
8524
|
},
|
|
7774
|
-
})
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
liveVideo: false,
|
|
7780
|
-
organizationId: this.organizationId,
|
|
7781
|
-
roomKey: this._roomKey,
|
|
7782
|
-
roomName: this.roomName,
|
|
7783
|
-
externalId: this.externalId,
|
|
8525
|
+
})
|
|
8526
|
+
.then(({ data }) => {
|
|
8527
|
+
return extractArray(data, "organizations", (organization) => {
|
|
8528
|
+
return Organization.fromJson(Object.assign({ permissions: {}, limits: {} }, assertRecord(organization, "organization")));
|
|
8529
|
+
});
|
|
7784
8530
|
});
|
|
7785
8531
|
}
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
}
|
|
7803
|
-
sendChatMessage(text) {
|
|
7804
|
-
this.signalSocket.emit("chat_message", {
|
|
7805
|
-
text,
|
|
8532
|
+
/**
|
|
8533
|
+
* Retrieves the organizations containing a user
|
|
8534
|
+
* with either the email or phoneNumber matching the logged in user.
|
|
8535
|
+
*
|
|
8536
|
+
* This is useful for showing the possible organization that the current
|
|
8537
|
+
* user could log in to.
|
|
8538
|
+
*/
|
|
8539
|
+
getOrganizationsByLoggedInUser() {
|
|
8540
|
+
return this._apiClient
|
|
8541
|
+
.request("/user/organizations", {
|
|
8542
|
+
method: "GET",
|
|
8543
|
+
})
|
|
8544
|
+
.then(({ data }) => {
|
|
8545
|
+
return extractArray(data, "organizations", (o) => {
|
|
8546
|
+
return Organization.fromJson(Object.assign({ permissions: {}, limits: {} }, assertRecord(o, "organization")));
|
|
8547
|
+
});
|
|
7806
8548
|
});
|
|
7807
8549
|
}
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
8550
|
+
/**
|
|
8551
|
+
* Checks if a subdomain is available and verifies its format.
|
|
8552
|
+
*/
|
|
8553
|
+
getSubdomainAvailability(subdomain) {
|
|
8554
|
+
assertString(subdomain, "subdomain");
|
|
8555
|
+
return this._apiClient
|
|
8556
|
+
.request(`/organization-subdomains/${encodeURIComponent(subdomain)}/availability`, {
|
|
8557
|
+
method: "GET",
|
|
8558
|
+
})
|
|
8559
|
+
.then(({ data }) => {
|
|
8560
|
+
assertInstanceOf(data, Object, "data");
|
|
8561
|
+
return {
|
|
8562
|
+
status: extractString(data, "status"),
|
|
8563
|
+
};
|
|
7814
8564
|
});
|
|
7815
8565
|
}
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
8566
|
+
/**
|
|
8567
|
+
* Updates preferences of the organization.
|
|
8568
|
+
*/
|
|
8569
|
+
updatePreferences({ organizationId, preferences, }) {
|
|
8570
|
+
assertTruthy(organizationId, "organizationId");
|
|
8571
|
+
assertTruthy(preferences, "preferences");
|
|
8572
|
+
return this._apiClient
|
|
8573
|
+
.request(`/organizations/${encodeURIComponent(organizationId)}/preferences`, {
|
|
8574
|
+
method: "PATCH",
|
|
8575
|
+
data: preferences,
|
|
8576
|
+
})
|
|
8577
|
+
.then(() => undefined);
|
|
7822
8578
|
}
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
8579
|
+
/**
|
|
8580
|
+
* Delete organization
|
|
8581
|
+
*/
|
|
8582
|
+
deleteOrganization({ organizationId }) {
|
|
8583
|
+
assertTruthy(organizationId, "organizationId");
|
|
8584
|
+
return this._apiClient
|
|
8585
|
+
.request(`/organizations/${encodeURIComponent(organizationId)}`, {
|
|
8586
|
+
method: "DELETE",
|
|
8587
|
+
})
|
|
8588
|
+
.then(() => undefined);
|
|
7829
8589
|
}
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
return;
|
|
7838
|
-
}
|
|
7839
|
-
const old = reportedStreamResolutions.get(streamId);
|
|
7840
|
-
if (!old || old.width !== width || old.height !== height) {
|
|
7841
|
-
this.rtcManager.updateStreamResolution(streamId, null, { width: width || 1, height: height || 1 });
|
|
7842
|
-
}
|
|
7843
|
-
reportedStreamResolutions.set(streamId, { width, height });
|
|
8590
|
+
}
|
|
8591
|
+
|
|
8592
|
+
class OrganizationServiceCache {
|
|
8593
|
+
constructor({ organizationService, subdomain }) {
|
|
8594
|
+
this._organizationService = organizationService;
|
|
8595
|
+
this._subdomain = subdomain;
|
|
8596
|
+
this._organizationPromise = null;
|
|
7844
8597
|
}
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
7848
|
-
const screenshareStream = this.localMedia.screenshareStream || (yield this.localMedia.startScreenshare());
|
|
7849
|
-
const onEnded = () => {
|
|
7850
|
-
this.stopScreenshare();
|
|
7851
|
-
};
|
|
7852
|
-
if ("oninactive" in screenshareStream) {
|
|
7853
|
-
// Chrome
|
|
7854
|
-
screenshareStream.addEventListener("inactive", onEnded);
|
|
7855
|
-
}
|
|
7856
|
-
else {
|
|
7857
|
-
// FF
|
|
7858
|
-
(_a = screenshareStream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.addEventListener("ended", onEnded);
|
|
7859
|
-
}
|
|
7860
|
-
(_b = this.rtcManager) === null || _b === void 0 ? void 0 : _b.addNewStream(screenshareStream.id, screenshareStream, false, true);
|
|
7861
|
-
this.screenshares = [
|
|
7862
|
-
...this.screenshares,
|
|
7863
|
-
{
|
|
7864
|
-
participantId: this.selfId || "",
|
|
7865
|
-
id: screenshareStream.id,
|
|
7866
|
-
hasAudioTrack: false,
|
|
7867
|
-
stream: screenshareStream,
|
|
7868
|
-
isLocal: true,
|
|
7869
|
-
},
|
|
7870
|
-
];
|
|
7871
|
-
this.dispatchEvent(new RoomConnectionEvent("screenshare_started", {
|
|
7872
|
-
detail: {
|
|
7873
|
-
participantId: this.selfId || "",
|
|
7874
|
-
id: screenshareStream.id,
|
|
7875
|
-
hasAudioTrack: false,
|
|
7876
|
-
stream: screenshareStream,
|
|
7877
|
-
isLocal: true,
|
|
7878
|
-
},
|
|
7879
|
-
}));
|
|
7880
|
-
});
|
|
8598
|
+
initOrganization() {
|
|
8599
|
+
return this.fetchOrganization().then(() => undefined);
|
|
7881
8600
|
}
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
const { id } = this.localMedia.screenshareStream;
|
|
7886
|
-
(_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.removeStream(id, this.localMedia.screenshareStream, null);
|
|
7887
|
-
this.screenshares = this.screenshares.filter((s) => s.id !== id);
|
|
7888
|
-
this.dispatchEvent(new RoomConnectionEvent("screenshare_stopped", { detail: { participantId: this.selfId || "", id } }));
|
|
7889
|
-
this.localMedia.stopScreenshare();
|
|
8601
|
+
fetchOrganization() {
|
|
8602
|
+
if (!this._organizationPromise) {
|
|
8603
|
+
this._organizationPromise = this._organizationService.getOrganizationBySubdomain(this._subdomain);
|
|
7890
8604
|
}
|
|
8605
|
+
return this._organizationPromise;
|
|
7891
8606
|
}
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
8607
|
+
}
|
|
8608
|
+
|
|
8609
|
+
const API_BASE_URL = "https://api.whereby.dev" ;
|
|
8610
|
+
function createServices() {
|
|
8611
|
+
const credentialsService = CredentialsService.create({ baseUrl: API_BASE_URL });
|
|
8612
|
+
const apiClient = new ApiClient({
|
|
8613
|
+
fetchDeviceCredentials: credentialsService.getCredentials.bind(credentialsService),
|
|
8614
|
+
baseUrl: API_BASE_URL,
|
|
8615
|
+
});
|
|
8616
|
+
const organizationService = new OrganizationService({ apiClient });
|
|
8617
|
+
const fetchOrganizationFromRoomUrl = (roomUrl) => {
|
|
8618
|
+
const roomUrlObj = new URL(roomUrl);
|
|
8619
|
+
const urls = fromLocation({ host: roomUrlObj.host });
|
|
8620
|
+
const organizationServiceCache = new OrganizationServiceCache({
|
|
8621
|
+
organizationService,
|
|
8622
|
+
subdomain: urls.subdomain,
|
|
7895
8623
|
});
|
|
7896
|
-
|
|
7897
|
-
}
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
8624
|
+
return organizationServiceCache.fetchOrganization();
|
|
8625
|
+
};
|
|
8626
|
+
return {
|
|
8627
|
+
credentialsService,
|
|
8628
|
+
apiClient,
|
|
8629
|
+
organizationService,
|
|
8630
|
+
fetchOrganizationFromRoomUrl,
|
|
8631
|
+
};
|
|
7901
8632
|
}
|
|
7902
8633
|
|
|
7903
|
-
const
|
|
8634
|
+
const selectRoomConnectionState = createSelector(selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants, (chatMessages, cloudRecording, localParticipant, localMediaStream, remoteParticipants, screenshares, connectionStatus, streaming, waitingParticipants) => {
|
|
8635
|
+
const state = {
|
|
8636
|
+
chatMessages,
|
|
8637
|
+
cloudRecording: cloudRecording.isRecording ? { status: "recording" } : undefined,
|
|
8638
|
+
localScreenshareStatus: localParticipant.isScreenSharing ? "active" : undefined,
|
|
8639
|
+
localParticipant: Object.assign(Object.assign({}, localParticipant), { stream: localMediaStream }),
|
|
8640
|
+
remoteParticipants,
|
|
8641
|
+
screenshares,
|
|
8642
|
+
connectionStatus,
|
|
8643
|
+
liveStream: streaming.isStreaming
|
|
8644
|
+
? {
|
|
8645
|
+
status: "streaming",
|
|
8646
|
+
startedAt: streaming.startedAt,
|
|
8647
|
+
}
|
|
8648
|
+
: undefined,
|
|
8649
|
+
waitingParticipants,
|
|
8650
|
+
};
|
|
8651
|
+
return state;
|
|
8652
|
+
});
|
|
8653
|
+
|
|
8654
|
+
const sdkVersion = "2.0.0-beta4";
|
|
8655
|
+
|
|
8656
|
+
const initialState$1 = {
|
|
7904
8657
|
chatMessages: [],
|
|
7905
8658
|
remoteParticipants: [],
|
|
7906
8659
|
connectionStatus: "initializing",
|
|
7907
8660
|
screenshares: [],
|
|
7908
8661
|
waitingParticipants: [],
|
|
7909
8662
|
};
|
|
7910
|
-
function updateParticipant(remoteParticipants, participantId, updates) {
|
|
7911
|
-
const existingParticipant = remoteParticipants.find((p) => p.id === participantId);
|
|
7912
|
-
if (!existingParticipant) {
|
|
7913
|
-
return remoteParticipants;
|
|
7914
|
-
}
|
|
7915
|
-
const index = remoteParticipants.indexOf(existingParticipant);
|
|
7916
|
-
return [
|
|
7917
|
-
...remoteParticipants.slice(0, index),
|
|
7918
|
-
Object.assign(Object.assign({}, existingParticipant), updates),
|
|
7919
|
-
...remoteParticipants.slice(index + 1),
|
|
7920
|
-
];
|
|
7921
|
-
}
|
|
7922
|
-
// omit the internal props
|
|
7923
|
-
function convertRemoteParticipantToRemoteParticipantState(p) {
|
|
7924
|
-
return {
|
|
7925
|
-
displayName: p.displayName,
|
|
7926
|
-
id: p.id,
|
|
7927
|
-
isAudioEnabled: p.isAudioEnabled,
|
|
7928
|
-
isLocalParticipant: p.isLocalParticipant,
|
|
7929
|
-
isVideoEnabled: p.isVideoEnabled,
|
|
7930
|
-
stream: p.stream,
|
|
7931
|
-
};
|
|
7932
|
-
}
|
|
7933
|
-
function addScreenshare(screenshares, screenshare) {
|
|
7934
|
-
const existingScreenshare = screenshares.find((ss) => ss.id === screenshare.id);
|
|
7935
|
-
if (existingScreenshare) {
|
|
7936
|
-
return screenshares;
|
|
7937
|
-
}
|
|
7938
|
-
return [...screenshares, screenshare];
|
|
7939
|
-
}
|
|
7940
|
-
function reducer(state, action) {
|
|
7941
|
-
switch (action.type) {
|
|
7942
|
-
case "CHAT_MESSAGE":
|
|
7943
|
-
return Object.assign(Object.assign({}, state), { chatMessages: [...state.chatMessages, action.payload] });
|
|
7944
|
-
case "CLOUD_RECORDING_REQUEST_STARTED":
|
|
7945
|
-
return Object.assign(Object.assign({}, state), { cloudRecording: {
|
|
7946
|
-
status: "requested",
|
|
7947
|
-
} });
|
|
7948
|
-
case "CLOUD_RECORDING_STARTED":
|
|
7949
|
-
return Object.assign(Object.assign({}, state), { cloudRecording: {
|
|
7950
|
-
status: action.payload.status,
|
|
7951
|
-
startedAt: action.payload.startedAt,
|
|
7952
|
-
} });
|
|
7953
|
-
case "CLOUD_RECORDING_STARTED_ERROR":
|
|
7954
|
-
return Object.assign(Object.assign({}, state), { cloudRecording: {
|
|
7955
|
-
status: action.payload.status,
|
|
7956
|
-
error: action.payload.error,
|
|
7957
|
-
} });
|
|
7958
|
-
case "CLOUD_RECORDING_STOPPED":
|
|
7959
|
-
delete state.cloudRecording;
|
|
7960
|
-
return Object.assign({}, state);
|
|
7961
|
-
case "ROOM_JOINED":
|
|
7962
|
-
return Object.assign(Object.assign({}, state), { localParticipant: action.payload.localParticipant, remoteParticipants: action.payload.remoteParticipants, waitingParticipants: action.payload.waitingParticipants, connectionStatus: "connected" });
|
|
7963
|
-
case "CONNECTION_STATUS_CHANGED":
|
|
7964
|
-
return Object.assign(Object.assign({}, state), { connectionStatus: action.payload.connectionStatus });
|
|
7965
|
-
case "PARTICIPANT_AUDIO_ENABLED":
|
|
7966
|
-
return Object.assign(Object.assign({}, state), { remoteParticipants: updateParticipant(state.remoteParticipants, action.payload.participantId, {
|
|
7967
|
-
isAudioEnabled: action.payload.isAudioEnabled,
|
|
7968
|
-
}) });
|
|
7969
|
-
case "PARTICIPANT_JOINED":
|
|
7970
|
-
return Object.assign(Object.assign({}, state), { remoteParticipants: [...state.remoteParticipants, action.payload.paritipant] });
|
|
7971
|
-
case "PARTICIPANT_LEFT":
|
|
7972
|
-
return Object.assign(Object.assign({}, state), { remoteParticipants: [...state.remoteParticipants.filter((p) => p.id !== action.payload.participantId)] });
|
|
7973
|
-
case "PARTICIPANT_STREAM_ADDED":
|
|
7974
|
-
return Object.assign(Object.assign({}, state), { remoteParticipants: updateParticipant(state.remoteParticipants, action.payload.participantId, {
|
|
7975
|
-
stream: action.payload.stream,
|
|
7976
|
-
}) });
|
|
7977
|
-
case "PARTICIPANT_VIDEO_ENABLED":
|
|
7978
|
-
return Object.assign(Object.assign({}, state), { remoteParticipants: updateParticipant(state.remoteParticipants, action.payload.participantId, {
|
|
7979
|
-
isVideoEnabled: action.payload.isVideoEnabled,
|
|
7980
|
-
}) });
|
|
7981
|
-
case "PARTICIPANT_METADATA_CHANGED":
|
|
7982
|
-
return Object.assign(Object.assign({}, state), { remoteParticipants: [
|
|
7983
|
-
...state.remoteParticipants.map((p) => p.id === action.payload.participantId ? Object.assign(Object.assign({}, p), { displayName: action.payload.displayName }) : p),
|
|
7984
|
-
] });
|
|
7985
|
-
case "LOCAL_CLIENT_DISPLAY_NAME_CHANGED":
|
|
7986
|
-
if (!state.localParticipant)
|
|
7987
|
-
return state;
|
|
7988
|
-
return Object.assign(Object.assign({}, state), { localParticipant: Object.assign(Object.assign({}, state.localParticipant), { displayName: action.payload.displayName }) });
|
|
7989
|
-
case "SCREENSHARE_STARTED":
|
|
7990
|
-
return Object.assign(Object.assign({}, state), { screenshares: addScreenshare(state.screenshares, {
|
|
7991
|
-
participantId: action.payload.participantId,
|
|
7992
|
-
id: action.payload.id,
|
|
7993
|
-
hasAudioTrack: action.payload.hasAudioTrack,
|
|
7994
|
-
stream: action.payload.stream,
|
|
7995
|
-
isLocal: action.payload.isLocal,
|
|
7996
|
-
}) });
|
|
7997
|
-
case "SCREENSHARE_STOPPED":
|
|
7998
|
-
return Object.assign(Object.assign({}, state), { screenshares: state.screenshares.filter((ss) => ss.id !== action.payload.id) });
|
|
7999
|
-
case "LOCAL_SCREENSHARE_START_ERROR":
|
|
8000
|
-
return Object.assign(Object.assign({}, state), { localScreenshareStatus: undefined });
|
|
8001
|
-
case "LOCAL_SCREENSHARE_STARTING":
|
|
8002
|
-
return Object.assign(Object.assign({}, state), { localScreenshareStatus: "starting" });
|
|
8003
|
-
case "LOCAL_SCREENSHARE_STARTED":
|
|
8004
|
-
return Object.assign(Object.assign({}, state), { localScreenshareStatus: "active" });
|
|
8005
|
-
case "LOCAL_SCREENSHARE_STOPPED":
|
|
8006
|
-
return Object.assign(Object.assign({}, state), { localScreenshareStatus: undefined, screenshares: state.screenshares.filter((ss) => !ss.isLocal) });
|
|
8007
|
-
case "LOCAL_CAMERA_ENABLED":
|
|
8008
|
-
if (!state.localParticipant)
|
|
8009
|
-
return state;
|
|
8010
|
-
return Object.assign(Object.assign({}, state), { localParticipant: Object.assign(Object.assign({}, state.localParticipant), { isVideoEnabled: action.payload }) });
|
|
8011
|
-
case "LOCAL_MICROPHONE_ENABLED":
|
|
8012
|
-
if (!state.localParticipant)
|
|
8013
|
-
return state;
|
|
8014
|
-
return Object.assign(Object.assign({}, state), { localParticipant: Object.assign(Object.assign({}, state.localParticipant), { isAudioEnabled: action.payload }) });
|
|
8015
|
-
case "STREAMING_STARTED":
|
|
8016
|
-
return Object.assign(Object.assign({}, state), { liveStream: {
|
|
8017
|
-
status: action.payload.status,
|
|
8018
|
-
startedAt: action.payload.startedAt,
|
|
8019
|
-
} });
|
|
8020
|
-
case "STREAMING_STOPPED":
|
|
8021
|
-
delete state.liveStream;
|
|
8022
|
-
return Object.assign({}, state);
|
|
8023
|
-
case "WAITING_PARTICIPANT_JOINED":
|
|
8024
|
-
return Object.assign(Object.assign({}, state), { waitingParticipants: [
|
|
8025
|
-
...state.waitingParticipants,
|
|
8026
|
-
{ id: action.payload.participantId, displayName: action.payload.displayName },
|
|
8027
|
-
] });
|
|
8028
|
-
case "WAITING_PARTICIPANT_LEFT":
|
|
8029
|
-
return Object.assign(Object.assign({}, state), { waitingParticipants: state.waitingParticipants.filter((wp) => wp.id !== action.payload.participantId) });
|
|
8030
|
-
default:
|
|
8031
|
-
throw state;
|
|
8032
|
-
}
|
|
8033
|
-
}
|
|
8034
8663
|
const defaultRoomConnectionOptions = {
|
|
8035
8664
|
localMediaOptions: {
|
|
8036
8665
|
audio: true,
|
|
@@ -8038,207 +8667,139 @@ const defaultRoomConnectionOptions = {
|
|
|
8038
8667
|
},
|
|
8039
8668
|
};
|
|
8040
8669
|
function useRoomConnection(roomUrl, roomConnectionOptions = defaultRoomConnectionOptions) {
|
|
8041
|
-
const [
|
|
8042
|
-
|
|
8043
|
-
|
|
8670
|
+
const [store] = React.useState(() => {
|
|
8671
|
+
if (roomConnectionOptions.localMedia) {
|
|
8672
|
+
return roomConnectionOptions.localMedia.store;
|
|
8673
|
+
}
|
|
8674
|
+
const services = createServices();
|
|
8675
|
+
return createStore({ injectServices: services });
|
|
8044
8676
|
});
|
|
8045
|
-
const [
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8677
|
+
const [boundVideoView, setBoundVideoView] = React.useState();
|
|
8678
|
+
const [roomConnectionState, setRoomConnectionState] = React.useState(initialState$1);
|
|
8679
|
+
React.useEffect(() => {
|
|
8680
|
+
const unsubscribe = observeStore(store, selectRoomConnectionState, setRoomConnectionState);
|
|
8681
|
+
const url = new URL(roomUrl); // Throw if invalid Whereby room url
|
|
8682
|
+
const searchParams = new URLSearchParams(url.search);
|
|
8683
|
+
const roomKey = searchParams.get("roomKey");
|
|
8684
|
+
store.dispatch(doAppJoin({
|
|
8685
|
+
displayName: roomConnectionOptions.displayName || "Guest",
|
|
8686
|
+
localMediaOptions: roomConnectionOptions.localMedia
|
|
8687
|
+
? undefined
|
|
8688
|
+
: roomConnectionOptions.localMediaOptions,
|
|
8689
|
+
roomKey,
|
|
8690
|
+
roomUrl,
|
|
8691
|
+
sdkVersion: sdkVersion,
|
|
8692
|
+
externalId: roomConnectionOptions.externalId || null,
|
|
8693
|
+
}));
|
|
8694
|
+
return () => {
|
|
8695
|
+
unsubscribe();
|
|
8696
|
+
store.dispatch(appLeft());
|
|
8051
8697
|
};
|
|
8052
|
-
}
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
dispatch({ type: "CLOUD_RECORDING_STARTED_ERROR", payload: e.detail });
|
|
8066
|
-
}),
|
|
8067
|
-
createEventListener("cloud_recording_stopped", () => {
|
|
8068
|
-
dispatch({ type: "CLOUD_RECORDING_STOPPED" });
|
|
8069
|
-
}),
|
|
8070
|
-
createEventListener("local_camera_enabled", (e) => {
|
|
8071
|
-
const { enabled } = e.detail;
|
|
8072
|
-
dispatch({ type: "LOCAL_CAMERA_ENABLED", payload: enabled });
|
|
8073
|
-
}),
|
|
8074
|
-
createEventListener("local_microphone_enabled", (e) => {
|
|
8075
|
-
const { enabled } = e.detail;
|
|
8076
|
-
dispatch({ type: "LOCAL_MICROPHONE_ENABLED", payload: enabled });
|
|
8077
|
-
}),
|
|
8078
|
-
createEventListener("participant_audio_enabled", (e) => {
|
|
8079
|
-
const { participantId, isAudioEnabled } = e.detail;
|
|
8080
|
-
dispatch({ type: "PARTICIPANT_AUDIO_ENABLED", payload: { participantId, isAudioEnabled } });
|
|
8081
|
-
}),
|
|
8082
|
-
createEventListener("participant_joined", (e) => {
|
|
8083
|
-
const { remoteParticipant } = e.detail;
|
|
8084
|
-
dispatch({
|
|
8085
|
-
type: "PARTICIPANT_JOINED",
|
|
8086
|
-
payload: {
|
|
8087
|
-
paritipant: convertRemoteParticipantToRemoteParticipantState(remoteParticipant),
|
|
8088
|
-
},
|
|
8089
|
-
});
|
|
8090
|
-
}),
|
|
8091
|
-
createEventListener("participant_left", (e) => {
|
|
8092
|
-
const { participantId } = e.detail;
|
|
8093
|
-
dispatch({ type: "PARTICIPANT_LEFT", payload: { participantId } });
|
|
8094
|
-
}),
|
|
8095
|
-
createEventListener("participant_metadata_changed", (e) => {
|
|
8096
|
-
const { participantId, displayName } = e.detail;
|
|
8097
|
-
dispatch({ type: "PARTICIPANT_METADATA_CHANGED", payload: { participantId, displayName } });
|
|
8098
|
-
}),
|
|
8099
|
-
createEventListener("participant_stream_added", (e) => {
|
|
8100
|
-
const { participantId, stream } = e.detail;
|
|
8101
|
-
dispatch({ type: "PARTICIPANT_STREAM_ADDED", payload: { participantId, stream } });
|
|
8102
|
-
}),
|
|
8103
|
-
createEventListener("participant_video_enabled", (e) => {
|
|
8104
|
-
const { participantId, isVideoEnabled } = e.detail;
|
|
8105
|
-
dispatch({ type: "PARTICIPANT_VIDEO_ENABLED", payload: { participantId, isVideoEnabled } });
|
|
8106
|
-
}),
|
|
8107
|
-
createEventListener("connection_status_changed", (e) => {
|
|
8108
|
-
const { connectionStatus } = e.detail;
|
|
8109
|
-
dispatch({
|
|
8110
|
-
type: "CONNECTION_STATUS_CHANGED",
|
|
8111
|
-
payload: { connectionStatus },
|
|
8112
|
-
});
|
|
8113
|
-
}),
|
|
8114
|
-
createEventListener("room_joined", (e) => {
|
|
8115
|
-
const { localParticipant, remoteParticipants, waitingParticipants } = e.detail;
|
|
8116
|
-
dispatch({
|
|
8117
|
-
type: "ROOM_JOINED",
|
|
8118
|
-
payload: {
|
|
8119
|
-
localParticipant,
|
|
8120
|
-
remoteParticipants: remoteParticipants.map(convertRemoteParticipantToRemoteParticipantState),
|
|
8121
|
-
waitingParticipants,
|
|
8122
|
-
},
|
|
8123
|
-
});
|
|
8124
|
-
}),
|
|
8125
|
-
createEventListener("screenshare_started", (e) => {
|
|
8126
|
-
const { participantId, stream, id, hasAudioTrack, isLocal } = e.detail;
|
|
8127
|
-
dispatch({
|
|
8128
|
-
type: "SCREENSHARE_STARTED",
|
|
8129
|
-
payload: { participantId, stream, id, hasAudioTrack, isLocal },
|
|
8130
|
-
});
|
|
8131
|
-
}),
|
|
8132
|
-
createEventListener("screenshare_stopped", (e) => {
|
|
8133
|
-
var _a;
|
|
8134
|
-
const { participantId, id } = e.detail;
|
|
8135
|
-
dispatch({
|
|
8136
|
-
type: "SCREENSHARE_STOPPED",
|
|
8137
|
-
payload: { participantId, id },
|
|
8138
|
-
});
|
|
8139
|
-
// dispach LOCAL_SCREENSHARE_STOPPED here because the exposed
|
|
8140
|
-
// stopScreenshare method is not called when the screenshare is
|
|
8141
|
-
// stopped by the browser's native stop screenshare button
|
|
8142
|
-
if (participantId === ((_a = state.localParticipant) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
8143
|
-
dispatch({ type: "LOCAL_SCREENSHARE_STOPPED" });
|
|
8144
|
-
}
|
|
8145
|
-
}),
|
|
8146
|
-
createEventListener("streaming_started", (e) => {
|
|
8147
|
-
const { status, startedAt } = e.detail;
|
|
8148
|
-
dispatch({ type: "STREAMING_STARTED", payload: { status, startedAt } });
|
|
8149
|
-
}),
|
|
8150
|
-
createEventListener("streaming_stopped", () => {
|
|
8151
|
-
dispatch({ type: "STREAMING_STOPPED" });
|
|
8152
|
-
}),
|
|
8153
|
-
createEventListener("waiting_participant_joined", (e) => {
|
|
8154
|
-
const { participantId, displayName } = e.detail;
|
|
8155
|
-
dispatch({
|
|
8156
|
-
type: "WAITING_PARTICIPANT_JOINED",
|
|
8157
|
-
payload: { participantId, displayName },
|
|
8158
|
-
});
|
|
8159
|
-
}),
|
|
8160
|
-
createEventListener("waiting_participant_left", (e) => {
|
|
8161
|
-
const { participantId } = e.detail;
|
|
8162
|
-
dispatch({
|
|
8163
|
-
type: "WAITING_PARTICIPANT_LEFT",
|
|
8164
|
-
payload: { participantId },
|
|
8698
|
+
}, []);
|
|
8699
|
+
React.useEffect(() => {
|
|
8700
|
+
if (store && !boundVideoView) {
|
|
8701
|
+
setBoundVideoView(() => (props) => {
|
|
8702
|
+
return React.createElement(VideoView, Object.assign({}, props, {
|
|
8703
|
+
onResize: ({ stream, width, height, }) => {
|
|
8704
|
+
store.dispatch(doRtcReportStreamResolution({
|
|
8705
|
+
streamId: stream.id,
|
|
8706
|
+
width,
|
|
8707
|
+
height,
|
|
8708
|
+
}));
|
|
8709
|
+
},
|
|
8710
|
+
}));
|
|
8165
8711
|
});
|
|
8166
|
-
}
|
|
8167
|
-
|
|
8712
|
+
}
|
|
8713
|
+
}, [store, boundVideoView]);
|
|
8714
|
+
const sendChatMessage = React.useCallback((text) => store.dispatch(doSendChatMessage({ text })), [store]);
|
|
8715
|
+
const knock = React.useCallback(() => store.dispatch(doKnockRoom()), [store]);
|
|
8716
|
+
const setDisplayName = React.useCallback((displayName) => store.dispatch(doSetDisplayName({ displayName })), [store]);
|
|
8717
|
+
const toggleCamera = React.useCallback((enabled) => store.dispatch(toggleCameraEnabled({ enabled })), [store]);
|
|
8718
|
+
const toggleMicrophone = React.useCallback((enabled) => store.dispatch(toggleMicrophoneEnabled({ enabled })), [store]);
|
|
8719
|
+
const acceptWaitingParticipant = React.useCallback((participantId) => store.dispatch(doAcceptWaitingParticipant({ participantId })), [store]);
|
|
8720
|
+
const rejectWaitingParticipant = React.useCallback((participantId) => store.dispatch(doRejectWaitingParticipant({ participantId })), [store]);
|
|
8721
|
+
const startCloudRecording = React.useCallback(() => store.dispatch(doStartCloudRecording()), [store]);
|
|
8722
|
+
const startScreenshare = React.useCallback(() => store.dispatch(doStartScreenshare()), [store]);
|
|
8723
|
+
const stopCloudRecording = React.useCallback(() => store.dispatch(doStopCloudRecording()), [store]);
|
|
8724
|
+
const stopScreenshare = React.useCallback(() => store.dispatch(doStopScreenshare()), [store]);
|
|
8725
|
+
return {
|
|
8726
|
+
state: roomConnectionState,
|
|
8727
|
+
actions: {
|
|
8728
|
+
sendChatMessage,
|
|
8729
|
+
knock,
|
|
8730
|
+
setDisplayName,
|
|
8731
|
+
toggleCamera,
|
|
8732
|
+
toggleMicrophone,
|
|
8733
|
+
acceptWaitingParticipant,
|
|
8734
|
+
rejectWaitingParticipant,
|
|
8735
|
+
startCloudRecording,
|
|
8736
|
+
startScreenshare,
|
|
8737
|
+
stopCloudRecording,
|
|
8738
|
+
stopScreenshare,
|
|
8739
|
+
},
|
|
8740
|
+
components: {
|
|
8741
|
+
VideoView: boundVideoView || VideoView,
|
|
8742
|
+
},
|
|
8743
|
+
_ref: store,
|
|
8744
|
+
};
|
|
8745
|
+
}
|
|
8746
|
+
|
|
8747
|
+
const selectLocalMediaState = createSelector(selectCameraDeviceError, selectCameraDevices, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsLocalMediaStarting, selectLocalMediaStream, selectMicrophoneDeviceError, selectMicrophoneDevices, selectSpeakerDevices, selectLocalMediaStartError, (cameraDeviceError, cameraDevices, currentCameraDeviceId, currentMicrophoneDeviceId, isSettingCameraDevice, isSettingMicrophoneDevice, isStarting, localStream, microphoneDeviceError, microphoneDevices, speakerDevices, startError) => {
|
|
8748
|
+
const state = {
|
|
8749
|
+
cameraDeviceError,
|
|
8750
|
+
cameraDevices,
|
|
8751
|
+
currentCameraDeviceId,
|
|
8752
|
+
currentMicrophoneDeviceId,
|
|
8753
|
+
isSettingCameraDevice,
|
|
8754
|
+
isSettingMicrophoneDevice,
|
|
8755
|
+
isStarting,
|
|
8756
|
+
localStream,
|
|
8757
|
+
microphoneDeviceError,
|
|
8758
|
+
microphoneDevices,
|
|
8759
|
+
speakerDevices,
|
|
8760
|
+
startError,
|
|
8761
|
+
};
|
|
8762
|
+
return state;
|
|
8763
|
+
});
|
|
8764
|
+
|
|
8765
|
+
const initialState = {
|
|
8766
|
+
cameraDeviceError: null,
|
|
8767
|
+
cameraDevices: [],
|
|
8768
|
+
isSettingCameraDevice: false,
|
|
8769
|
+
isSettingMicrophoneDevice: false,
|
|
8770
|
+
isStarting: false,
|
|
8771
|
+
microphoneDeviceError: null,
|
|
8772
|
+
microphoneDevices: [],
|
|
8773
|
+
speakerDevices: [],
|
|
8774
|
+
startError: null,
|
|
8775
|
+
};
|
|
8776
|
+
function useLocalMedia(optionsOrStream = { audio: true, video: true }) {
|
|
8777
|
+
const [store] = useState(() => {
|
|
8778
|
+
const services = createServices();
|
|
8779
|
+
return createStore({ injectServices: services });
|
|
8780
|
+
});
|
|
8781
|
+
const [localMediaState, setLocalMediaState] = useState(initialState);
|
|
8168
8782
|
useEffect(() => {
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
});
|
|
8172
|
-
roomConnection.join();
|
|
8783
|
+
const unsubscribe = observeStore(store, selectLocalMediaState, setLocalMediaState);
|
|
8784
|
+
store.dispatch(doStartLocalMedia(optionsOrStream));
|
|
8173
8785
|
return () => {
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
});
|
|
8177
|
-
roomConnection.leave();
|
|
8786
|
+
unsubscribe();
|
|
8787
|
+
store.dispatch(doStopLocalMedia());
|
|
8178
8788
|
};
|
|
8179
8789
|
}, []);
|
|
8790
|
+
const setCameraDevice = useCallback((deviceId) => store.dispatch(setCurrentCameraDeviceId({ deviceId })), [store]);
|
|
8791
|
+
const setMicrophoneDevice = useCallback((deviceId) => store.dispatch(setCurrentMicrophoneDeviceId({ deviceId })), [store]);
|
|
8792
|
+
const toggleCamera = useCallback((enabled) => store.dispatch(toggleCameraEnabled({ enabled })), [store]);
|
|
8793
|
+
const toggleMicrophone = useCallback((enabled) => store.dispatch(toggleMicrophoneEnabled({ enabled })), [store]);
|
|
8180
8794
|
return {
|
|
8181
|
-
state,
|
|
8795
|
+
state: localMediaState,
|
|
8182
8796
|
actions: {
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
roomConnection.sendChatMessage(text);
|
|
8188
|
-
},
|
|
8189
|
-
setDisplayName: (displayName) => {
|
|
8190
|
-
roomConnection.setDisplayName(displayName);
|
|
8191
|
-
dispatch({ type: "LOCAL_CLIENT_DISPLAY_NAME_CHANGED", payload: { displayName } });
|
|
8192
|
-
},
|
|
8193
|
-
toggleCamera: (enabled) => {
|
|
8194
|
-
roomConnection.localMedia.toggleCameraEnabled(enabled);
|
|
8195
|
-
},
|
|
8196
|
-
toggleMicrophone: (enabled) => {
|
|
8197
|
-
roomConnection.localMedia.toggleMichrophoneEnabled(enabled);
|
|
8198
|
-
},
|
|
8199
|
-
acceptWaitingParticipant: (participantId) => {
|
|
8200
|
-
roomConnection.acceptWaitingParticipant(participantId);
|
|
8201
|
-
},
|
|
8202
|
-
rejectWaitingParticipant: (participantId) => {
|
|
8203
|
-
roomConnection.rejectWaitingParticipant(participantId);
|
|
8204
|
-
},
|
|
8205
|
-
startCloudRecording: () => {
|
|
8206
|
-
var _a;
|
|
8207
|
-
// don't start recording if it's already started or requested
|
|
8208
|
-
if (state.cloudRecording && ["recording", "requested"].includes((_a = state.cloudRecording) === null || _a === void 0 ? void 0 : _a.status)) {
|
|
8209
|
-
return;
|
|
8210
|
-
}
|
|
8211
|
-
roomConnection.startCloudRecording();
|
|
8212
|
-
},
|
|
8213
|
-
stopCloudRecording: () => {
|
|
8214
|
-
roomConnection.stopCloudRecording();
|
|
8215
|
-
},
|
|
8216
|
-
startScreenshare: () => __awaiter(this, void 0, void 0, function* () {
|
|
8217
|
-
dispatch({ type: "LOCAL_SCREENSHARE_STARTING" });
|
|
8218
|
-
try {
|
|
8219
|
-
yield roomConnection.startScreenshare();
|
|
8220
|
-
dispatch({ type: "LOCAL_SCREENSHARE_STARTED" });
|
|
8221
|
-
}
|
|
8222
|
-
catch (error) {
|
|
8223
|
-
dispatch({ type: "LOCAL_SCREENSHARE_START_ERROR", payload: error });
|
|
8224
|
-
}
|
|
8225
|
-
}),
|
|
8226
|
-
stopScreenshare: () => {
|
|
8227
|
-
roomConnection.stopScreenshare();
|
|
8228
|
-
},
|
|
8229
|
-
},
|
|
8230
|
-
components: {
|
|
8231
|
-
VideoView: (props) => React.createElement(VideoView, Object.assign({}, props, {
|
|
8232
|
-
onResize: ({ stream, width, height, }) => {
|
|
8233
|
-
roomConnection.updateStreamResolution({
|
|
8234
|
-
streamId: stream.id,
|
|
8235
|
-
width,
|
|
8236
|
-
height,
|
|
8237
|
-
});
|
|
8238
|
-
},
|
|
8239
|
-
})),
|
|
8797
|
+
setCameraDevice,
|
|
8798
|
+
setMicrophoneDevice,
|
|
8799
|
+
toggleCameraEnabled: toggleCamera,
|
|
8800
|
+
toggleMicrophoneEnabled: toggleMicrophone,
|
|
8240
8801
|
},
|
|
8241
|
-
|
|
8802
|
+
store,
|
|
8242
8803
|
};
|
|
8243
8804
|
}
|
|
8244
8805
|
|