@whereby.com/browser-sdk 2.0.0-alpha9 → 2.0.0-beta1
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 +77 -26
- package/dist/LocalMedia.d.ts +63 -0
- package/dist/LocalMedia.js +211 -0
- package/dist/RoomConnection.d.ts +184 -0
- package/dist/RoomConnection.js +627 -0
- package/dist/RoomParticipant.d.ts +50 -0
- package/dist/RoomParticipant.js +48 -0
- package/dist/api/ApiClient.d.ts +26 -0
- package/dist/api/ApiClient.js +63 -0
- package/dist/api/Credentials.d.ts +17 -0
- package/dist/api/Credentials.js +16 -0
- package/dist/api/HttpClient.d.ts +16 -0
- package/dist/api/HttpClient.js +53 -0
- package/dist/api/MultipartHttpClient.d.ts +10 -0
- package/dist/api/MultipartHttpClient.js +25 -0
- package/dist/api/OrganizationApiClient.d.ts +16 -0
- package/dist/api/OrganizationApiClient.js +29 -0
- package/dist/api/Response.d.ts +29 -0
- package/dist/api/Response.js +9 -0
- package/dist/api/credentialsService/index.d.ts +27 -0
- package/dist/api/credentialsService/index.js +89 -0
- package/dist/api/deviceService/index.d.ts +9 -0
- package/dist/api/deviceService/index.js +23 -0
- package/dist/api/extractUtils.d.ts +16 -0
- package/dist/api/extractUtils.js +51 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.js +7 -0
- package/dist/api/localStorageWrapper/index.d.ts +2 -0
- package/dist/api/localStorageWrapper/index.js +15 -0
- package/dist/api/models/Account.d.ts +20 -0
- package/dist/api/models/Account.js +24 -0
- package/dist/api/models/Meeting.d.ts +12 -0
- package/dist/api/models/Meeting.js +29 -0
- package/dist/api/models/Organization.d.ts +102 -0
- package/dist/api/models/Organization.js +81 -0
- package/dist/api/models/Room.d.ts +4 -0
- package/dist/api/models/Room.js +38 -0
- package/dist/api/models/account/EmbeddedFreeTierStatus.d.ts +13 -0
- package/dist/api/models/account/EmbeddedFreeTierStatus.js +17 -0
- package/dist/api/modules/AbstractStore.d.ts +5 -0
- package/dist/api/modules/AbstractStore.js +1 -0
- package/dist/api/modules/ChromeStorageStore.d.ts +10 -0
- package/dist/api/modules/ChromeStorageStore.js +21 -0
- package/dist/api/modules/LocalStorageStore.d.ts +9 -0
- package/dist/api/modules/LocalStorageStore.js +35 -0
- package/dist/api/modules/tests/__mocks__/storage.d.ts +10 -0
- package/dist/api/modules/tests/__mocks__/storage.js +19 -0
- package/dist/api/organizationService/index.d.ts +46 -0
- package/dist/api/organizationService/index.js +158 -0
- package/dist/api/organizationServiceCache/index.d.ts +13 -0
- package/dist/api/organizationServiceCache/index.js +16 -0
- package/dist/api/parameterAssertUtils.d.ts +13 -0
- package/dist/api/parameterAssertUtils.js +64 -0
- package/dist/api/roomService/index.d.ts +54 -0
- package/dist/api/roomService/index.js +160 -0
- package/dist/api/test/helpers.d.ts +7 -0
- package/dist/api/test/helpers.js +32 -0
- package/dist/api/types.d.ts +5 -0
- package/dist/api/types.js +1 -0
- package/dist/embed/index.d.ts +32 -0
- package/dist/embed/index.js +125 -0
- package/dist/react/VideoView.d.ts +15 -0
- package/dist/react/VideoView.js +37 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +4 -0
- package/dist/react/useLocalMedia.d.ts +28 -0
- package/dist/react/useLocalMedia.js +109 -0
- package/dist/react/useRoomConnection.d.ts +55 -0
- package/dist/react/useRoomConnection.js +315 -0
- package/dist/utils/debounce.d.ts +9 -0
- package/dist/utils/debounce.js +20 -0
- package/dist/utils/fakeAudioStream.d.ts +1 -0
- package/dist/utils/fakeAudioStream.js +18 -0
- package/dist/utils/fakeWebcamFrame.d.ts +1 -0
- package/dist/utils/fakeWebcamFrame.js +49 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/v2-beta1.js +2001 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +97 -81
- package/dist/lib.cjs.js +0 -5868
- package/dist/lib.esm.js +0 -5850
- package/dist/types.d.ts +0 -308
- package/dist/v2-alpha9.js +0 -43
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import { __awaiter } from "tslib";
|
|
2
|
+
import RtcManagerDispatcher from "@whereby/jslib-media/src/webrtc/RtcManagerDispatcher";
|
|
3
|
+
import { fromLocation } from "@whereby/jslib-media/src/utils/urls";
|
|
4
|
+
import { ApiClient, CredentialsService, OrganizationApiClient, OrganizationService, OrganizationServiceCache, RoomService, } from "./api";
|
|
5
|
+
import { LocalParticipant, RemoteParticipant } from "./RoomParticipant";
|
|
6
|
+
import ServerSocket from "@whereby/jslib-media/src/utils/ServerSocket";
|
|
7
|
+
import { sdkVersion } from "./version";
|
|
8
|
+
import LocalMedia from "./LocalMedia";
|
|
9
|
+
class RoomConnectionEvent extends CustomEvent {
|
|
10
|
+
constructor(eventType, eventInitDict) {
|
|
11
|
+
super(eventType, eventInitDict);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const API_BASE_URL = process.env["REACT_APP_API_BASE_URL"] || "https://api.whereby.dev";
|
|
15
|
+
const SIGNAL_BASE_URL = process.env["REACT_APP_SIGNAL_BASE_URL"] || "wss://signal.appearin.net";
|
|
16
|
+
const NON_PERSON_ROLES = ["recorder", "streamer"];
|
|
17
|
+
const reportedStreamResolutions = new Map();
|
|
18
|
+
function createSocket() {
|
|
19
|
+
const parsedUrl = new URL(SIGNAL_BASE_URL);
|
|
20
|
+
const socketHost = parsedUrl.origin;
|
|
21
|
+
const socketOverrides = {
|
|
22
|
+
autoConnect: false,
|
|
23
|
+
};
|
|
24
|
+
return new ServerSocket(socketHost, socketOverrides);
|
|
25
|
+
}
|
|
26
|
+
export function handleStreamAdded(remoteParticipants, { clientId, stream, streamId, streamType }) {
|
|
27
|
+
if (!streamId) {
|
|
28
|
+
streamId = stream.id;
|
|
29
|
+
}
|
|
30
|
+
const remoteParticipant = remoteParticipants.find((p) => p.id === clientId);
|
|
31
|
+
if (!remoteParticipant) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const remoteParticipantStream = remoteParticipant.streams.find((s) => s.id === streamId) || remoteParticipant.streams[0];
|
|
35
|
+
if ((remoteParticipant.stream && remoteParticipant.stream.id === streamId) ||
|
|
36
|
+
(!remoteParticipant.stream && streamType === "webcam") ||
|
|
37
|
+
(!remoteParticipant.stream && !streamType && remoteParticipant.streams.indexOf(remoteParticipantStream) < 1)) {
|
|
38
|
+
return new RoomConnectionEvent("participant_stream_added", {
|
|
39
|
+
detail: { participantId: clientId, stream, streamId },
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return new RoomConnectionEvent("screenshare_started", {
|
|
43
|
+
detail: {
|
|
44
|
+
participantId: clientId,
|
|
45
|
+
stream,
|
|
46
|
+
id: streamId,
|
|
47
|
+
isLocal: false,
|
|
48
|
+
hasAudioTrack: stream.getAudioTracks().length > 0,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const noop = () => {
|
|
53
|
+
return;
|
|
54
|
+
};
|
|
55
|
+
const TypedEventTarget = EventTarget;
|
|
56
|
+
export default class RoomConnection extends TypedEventTarget {
|
|
57
|
+
constructor(roomUrl, { displayName, localMedia, localMediaConstraints, logger, roomKey, externalId }) {
|
|
58
|
+
super();
|
|
59
|
+
this.remoteParticipants = [];
|
|
60
|
+
this.screenshares = [];
|
|
61
|
+
this._deviceCredentials = null;
|
|
62
|
+
this._ownsLocalMedia = false;
|
|
63
|
+
this.organizationId = "";
|
|
64
|
+
this.connectionStatus = "initializing";
|
|
65
|
+
this.selfId = null;
|
|
66
|
+
this.roomUrl = new URL(roomUrl);
|
|
67
|
+
const searchParams = new URLSearchParams(this.roomUrl.search);
|
|
68
|
+
this._roomKey = roomKey || searchParams.get("roomKey");
|
|
69
|
+
this.roomName = this.roomUrl.pathname;
|
|
70
|
+
this.logger = logger || {
|
|
71
|
+
debug: noop,
|
|
72
|
+
error: noop,
|
|
73
|
+
log: noop,
|
|
74
|
+
warn: noop,
|
|
75
|
+
};
|
|
76
|
+
this.displayName = displayName;
|
|
77
|
+
this.externalId = externalId;
|
|
78
|
+
this.localMediaConstraints = localMediaConstraints;
|
|
79
|
+
const urls = fromLocation({ host: this.roomUrl.host });
|
|
80
|
+
if (localMedia) {
|
|
81
|
+
this.localMedia = localMedia;
|
|
82
|
+
}
|
|
83
|
+
else if (localMediaConstraints) {
|
|
84
|
+
this.localMedia = new LocalMedia(localMediaConstraints);
|
|
85
|
+
this._ownsLocalMedia = true;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
throw new Error("Missing constraints");
|
|
89
|
+
}
|
|
90
|
+
this.credentialsService = CredentialsService.create({ baseUrl: API_BASE_URL });
|
|
91
|
+
this.apiClient = new ApiClient({
|
|
92
|
+
fetchDeviceCredentials: this.credentialsService.getCredentials.bind(this.credentialsService),
|
|
93
|
+
baseUrl: API_BASE_URL,
|
|
94
|
+
});
|
|
95
|
+
this.organizationService = new OrganizationService({ apiClient: this.apiClient });
|
|
96
|
+
this.organizationServiceCache = new OrganizationServiceCache({
|
|
97
|
+
organizationService: this.organizationService,
|
|
98
|
+
subdomain: urls.subdomain,
|
|
99
|
+
});
|
|
100
|
+
this.organizationApiClient = new OrganizationApiClient({
|
|
101
|
+
apiClient: this.apiClient,
|
|
102
|
+
fetchOrganization: () => __awaiter(this, void 0, void 0, function* () {
|
|
103
|
+
const organization = yield this.organizationServiceCache.fetchOrganization();
|
|
104
|
+
return organization || undefined;
|
|
105
|
+
}),
|
|
106
|
+
});
|
|
107
|
+
this.roomService = new RoomService({ organizationApiClient: this.organizationApiClient });
|
|
108
|
+
this.signalSocket = createSocket();
|
|
109
|
+
this.signalSocket.on("new_client", this._handleNewClient.bind(this));
|
|
110
|
+
this.signalSocket.on("chat_message", this._handleNewChatMessage.bind(this));
|
|
111
|
+
this.signalSocket.on("client_left", this._handleClientLeft.bind(this));
|
|
112
|
+
this.signalSocket.on("audio_enabled", this._handleClientAudioEnabled.bind(this));
|
|
113
|
+
this.signalSocket.on("video_enabled", this._handleClientVideoEnabled.bind(this));
|
|
114
|
+
this.signalSocket.on("client_metadata_received", this._handleClientMetadataReceived.bind(this));
|
|
115
|
+
this.signalSocket.on("knock_handled", this._handleKnockHandled.bind(this));
|
|
116
|
+
this.signalSocket.on("knocker_left", this._handleKnockerLeft.bind(this));
|
|
117
|
+
this.signalSocket.on("room_joined", this._handleRoomJoined.bind(this));
|
|
118
|
+
this.signalSocket.on("room_knocked", this._handleRoomKnocked.bind(this));
|
|
119
|
+
this.signalSocket.on("cloud_recording_stopped", this._handleCloudRecordingStopped.bind(this));
|
|
120
|
+
this.signalSocket.on("screenshare_started", this._handleScreenshareStarted.bind(this));
|
|
121
|
+
this.signalSocket.on("screenshare_stopped", this._handleScreenshareStopped.bind(this));
|
|
122
|
+
this.signalSocket.on("streaming_stopped", this._handleStreamingStopped.bind(this));
|
|
123
|
+
this.signalSocket.on("disconnect", this._handleDisconnect.bind(this));
|
|
124
|
+
this.signalSocket.on("connect_error", this._handleDisconnect.bind(this));
|
|
125
|
+
this.signalSocketManager = this.signalSocket.getManager();
|
|
126
|
+
this.signalSocketManager.on("reconnect", this._handleReconnect.bind(this));
|
|
127
|
+
this.localMedia.addEventListener("camera_enabled", (e) => {
|
|
128
|
+
const { enabled } = e.detail;
|
|
129
|
+
this.signalSocket.emit("enable_video", { enabled });
|
|
130
|
+
this.dispatchEvent(new RoomConnectionEvent("local_camera_enabled", { detail: { enabled } }));
|
|
131
|
+
});
|
|
132
|
+
this.localMedia.addEventListener("microphone_enabled", (e) => {
|
|
133
|
+
const { enabled } = e.detail;
|
|
134
|
+
this.signalSocket.emit("enable_audio", { enabled });
|
|
135
|
+
this.dispatchEvent(new RoomConnectionEvent("local_microphone_enabled", { detail: { enabled } }));
|
|
136
|
+
});
|
|
137
|
+
const webrtcProvider = {
|
|
138
|
+
getMediaConstraints: () => ({
|
|
139
|
+
audio: this.localMedia.isMicrophoneEnabled(),
|
|
140
|
+
video: this.localMedia.isCameraEnabled(),
|
|
141
|
+
}),
|
|
142
|
+
deferrable(clientId) {
|
|
143
|
+
return !clientId;
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
this.rtcManagerDispatcher = new RtcManagerDispatcher({
|
|
147
|
+
emitter: {
|
|
148
|
+
emit: this._handleRtcEvent.bind(this),
|
|
149
|
+
},
|
|
150
|
+
serverSocket: this.signalSocket,
|
|
151
|
+
webrtcProvider,
|
|
152
|
+
features: {
|
|
153
|
+
lowDataModeEnabled: false,
|
|
154
|
+
sfuServerOverrideHost: undefined,
|
|
155
|
+
turnServerOverrideHost: undefined,
|
|
156
|
+
useOnlyTURN: undefined,
|
|
157
|
+
vp9On: false,
|
|
158
|
+
h264On: false,
|
|
159
|
+
simulcastScreenshareOn: false,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
get roomKey() {
|
|
164
|
+
return this._roomKey;
|
|
165
|
+
}
|
|
166
|
+
_handleNewChatMessage(message) {
|
|
167
|
+
this.dispatchEvent(new RoomConnectionEvent("chat_message", { detail: message }));
|
|
168
|
+
}
|
|
169
|
+
_handleCloudRecordingStarted({ client }) {
|
|
170
|
+
this.dispatchEvent(new RoomConnectionEvent("cloud_recording_started", {
|
|
171
|
+
detail: {
|
|
172
|
+
status: "recording",
|
|
173
|
+
startedAt: client.startedCloudRecordingAt
|
|
174
|
+
? new Date(client.startedCloudRecordingAt).getTime()
|
|
175
|
+
: new Date().getTime(),
|
|
176
|
+
},
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
_handleStreamingStarted() {
|
|
180
|
+
this.dispatchEvent(new RoomConnectionEvent("streaming_started", {
|
|
181
|
+
detail: {
|
|
182
|
+
status: "streaming",
|
|
183
|
+
startedAt: new Date().getTime(),
|
|
184
|
+
},
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
_handleNewClient({ client }) {
|
|
188
|
+
if (client.role.roleName === "recorder") {
|
|
189
|
+
this._handleCloudRecordingStarted({ client });
|
|
190
|
+
}
|
|
191
|
+
if (client.role.roleName === "streamer") {
|
|
192
|
+
this._handleStreamingStarted();
|
|
193
|
+
}
|
|
194
|
+
if (NON_PERSON_ROLES.includes(client.role.roleName)) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const remoteParticipant = new RemoteParticipant(Object.assign(Object.assign({}, client), { newJoiner: true }));
|
|
198
|
+
this.remoteParticipants = [...this.remoteParticipants, remoteParticipant];
|
|
199
|
+
this._handleAcceptStreams([remoteParticipant]);
|
|
200
|
+
this.dispatchEvent(new RoomConnectionEvent("participant_joined", {
|
|
201
|
+
detail: { remoteParticipant },
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
_handleClientLeft({ clientId }) {
|
|
205
|
+
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
206
|
+
this.remoteParticipants = this.remoteParticipants.filter((p) => p.id !== clientId);
|
|
207
|
+
if (!remoteParticipant) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
this.dispatchEvent(new RoomConnectionEvent("participant_left", { detail: { participantId: remoteParticipant.id } }));
|
|
211
|
+
}
|
|
212
|
+
_handleClientAudioEnabled({ clientId, isAudioEnabled }) {
|
|
213
|
+
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
214
|
+
if (!remoteParticipant) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.dispatchEvent(new RoomConnectionEvent("participant_audio_enabled", {
|
|
218
|
+
detail: { participantId: remoteParticipant.id, isAudioEnabled },
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
_handleClientVideoEnabled({ clientId, isVideoEnabled }) {
|
|
222
|
+
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
223
|
+
if (!remoteParticipant) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this.dispatchEvent(new RoomConnectionEvent("participant_video_enabled", {
|
|
227
|
+
detail: { participantId: remoteParticipant.id, isVideoEnabled },
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
_handleClientMetadataReceived({ payload: { clientId, displayName } }) {
|
|
231
|
+
const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
|
|
232
|
+
if (!remoteParticipant) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
this.dispatchEvent(new RoomConnectionEvent("participant_metadata_changed", {
|
|
236
|
+
detail: { participantId: remoteParticipant.id, displayName },
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
_handleKnockHandled(payload) {
|
|
240
|
+
const { clientId, resolution } = payload;
|
|
241
|
+
if (clientId !== this.selfId) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (resolution === "accepted") {
|
|
245
|
+
this._roomKey = payload.metadata.roomKey;
|
|
246
|
+
this._joinRoom();
|
|
247
|
+
}
|
|
248
|
+
else if (resolution === "rejected") {
|
|
249
|
+
this.connectionStatus = "knock_rejected";
|
|
250
|
+
this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
|
|
251
|
+
detail: {
|
|
252
|
+
connectionStatus: this.connectionStatus,
|
|
253
|
+
},
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
_handleKnockerLeft(payload) {
|
|
258
|
+
const { clientId } = payload;
|
|
259
|
+
this.dispatchEvent(new RoomConnectionEvent("waiting_participant_left", {
|
|
260
|
+
detail: { participantId: clientId },
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
_handleRoomJoined(event) {
|
|
264
|
+
const { error, isLocked, room, selfId } = event;
|
|
265
|
+
this.selfId = selfId;
|
|
266
|
+
if (error === "room_locked" && isLocked) {
|
|
267
|
+
this.connectionStatus = "room_locked";
|
|
268
|
+
this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
|
|
269
|
+
detail: {
|
|
270
|
+
connectionStatus: this.connectionStatus,
|
|
271
|
+
},
|
|
272
|
+
}));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (room) {
|
|
276
|
+
const { clients, knockers } = room;
|
|
277
|
+
const localClient = clients.find((c) => c.id === selfId);
|
|
278
|
+
if (!localClient)
|
|
279
|
+
throw new Error("Missing local client");
|
|
280
|
+
this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localMedia.stream || undefined }));
|
|
281
|
+
const recorderClient = clients.find((c) => c.role.roleName === "recorder");
|
|
282
|
+
if (recorderClient) {
|
|
283
|
+
this._handleCloudRecordingStarted({ client: recorderClient });
|
|
284
|
+
}
|
|
285
|
+
const streamerClient = clients.find((c) => c.role.roleName === "streamer");
|
|
286
|
+
if (streamerClient) {
|
|
287
|
+
this._handleStreamingStarted();
|
|
288
|
+
}
|
|
289
|
+
this.remoteParticipants = clients
|
|
290
|
+
.filter((c) => c.id !== selfId)
|
|
291
|
+
.filter((c) => !NON_PERSON_ROLES.includes(c.role.roleName))
|
|
292
|
+
.map((c) => new RemoteParticipant(Object.assign(Object.assign({}, c), { newJoiner: false })));
|
|
293
|
+
this.connectionStatus = "connected";
|
|
294
|
+
this.dispatchEvent(new RoomConnectionEvent("room_joined", {
|
|
295
|
+
detail: {
|
|
296
|
+
localParticipant: this.localParticipant,
|
|
297
|
+
remoteParticipants: this.remoteParticipants,
|
|
298
|
+
waitingParticipants: knockers.map((knocker) => {
|
|
299
|
+
return { id: knocker.clientId, displayName: knocker.displayName };
|
|
300
|
+
}),
|
|
301
|
+
},
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
_handleRoomKnocked(event) {
|
|
306
|
+
const { clientId, displayName } = event;
|
|
307
|
+
this.dispatchEvent(new RoomConnectionEvent("waiting_participant_joined", {
|
|
308
|
+
detail: { participantId: clientId, displayName },
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
_handleReconnect() {
|
|
312
|
+
this.logger.log("Reconnected to signal socket");
|
|
313
|
+
this.signalSocket.emit("identify_device", { deviceCredentials: this._deviceCredentials });
|
|
314
|
+
this.signalSocket.once("device_identified", () => {
|
|
315
|
+
this._joinRoom();
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
_handleDisconnect() {
|
|
319
|
+
this.connectionStatus = "disconnected";
|
|
320
|
+
this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
|
|
321
|
+
detail: {
|
|
322
|
+
connectionStatus: this.connectionStatus,
|
|
323
|
+
},
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
_handleCloudRecordingStopped() {
|
|
327
|
+
this.dispatchEvent(new RoomConnectionEvent("cloud_recording_stopped"));
|
|
328
|
+
}
|
|
329
|
+
_handleStreamingStopped() {
|
|
330
|
+
this.dispatchEvent(new RoomConnectionEvent("streaming_stopped"));
|
|
331
|
+
}
|
|
332
|
+
_handleScreenshareStarted(screenshare) {
|
|
333
|
+
const { clientId: participantId, streamId: id, hasAudioTrack } = screenshare;
|
|
334
|
+
const remoteParticipant = this.remoteParticipants.find((p) => p.id === participantId);
|
|
335
|
+
if (!remoteParticipant) {
|
|
336
|
+
this.logger.log("WARN: Could not find participant for screenshare");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const foundScreenshare = this.screenshares.find((s) => s.id === id);
|
|
340
|
+
if (foundScreenshare) {
|
|
341
|
+
this.logger.log("WARN: Screenshare already exists");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
remoteParticipant.addStream(id, "to_accept");
|
|
345
|
+
this._handleAcceptStreams([remoteParticipant]);
|
|
346
|
+
this.screenshares = [
|
|
347
|
+
...this.screenshares,
|
|
348
|
+
{ participantId, id, hasAudioTrack, stream: undefined, isLocal: false },
|
|
349
|
+
];
|
|
350
|
+
}
|
|
351
|
+
_handleScreenshareStopped(screenshare) {
|
|
352
|
+
const { clientId: participantId, streamId: id } = screenshare;
|
|
353
|
+
const remoteParticipant = this.remoteParticipants.find((p) => p.id === participantId);
|
|
354
|
+
if (!remoteParticipant) {
|
|
355
|
+
this.logger.log("WARN: Could not find participant for screenshare");
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
remoteParticipant.removeStream(id);
|
|
359
|
+
this.screenshares = this.screenshares.filter((s) => !(s.participantId === participantId && s.id === id));
|
|
360
|
+
this.dispatchEvent(new RoomConnectionEvent("screenshare_stopped", { detail: { participantId, id } }));
|
|
361
|
+
}
|
|
362
|
+
_handleRtcEvent(eventName, data) {
|
|
363
|
+
if (eventName === "rtc_manager_created") {
|
|
364
|
+
return this._handleRtcManagerCreated(data);
|
|
365
|
+
}
|
|
366
|
+
else if (eventName === "stream_added") {
|
|
367
|
+
return this._handleStreamAdded(data);
|
|
368
|
+
}
|
|
369
|
+
else if (eventName === "rtc_manager_destroyed") {
|
|
370
|
+
return this._handleRtcManagerDestroyed();
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
this.logger.log(`Unhandled RTC event ${eventName}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
_handleRtcManagerCreated({ rtcManager }) {
|
|
377
|
+
var _a;
|
|
378
|
+
this.rtcManager = rtcManager;
|
|
379
|
+
this.localMedia.addRtcManager(rtcManager);
|
|
380
|
+
if (this.localMedia.stream) {
|
|
381
|
+
(_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.addNewStream("0", this.localMedia.stream, !this.localMedia.isMicrophoneEnabled(), !this.localMedia.isCameraEnabled());
|
|
382
|
+
}
|
|
383
|
+
if (this.remoteParticipants.length) {
|
|
384
|
+
this._handleAcceptStreams(this.remoteParticipants);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
_handleRtcManagerDestroyed() {
|
|
388
|
+
this.rtcManager = undefined;
|
|
389
|
+
}
|
|
390
|
+
_handleAcceptStreams(remoteParticipants) {
|
|
391
|
+
var _a, _b;
|
|
392
|
+
if (!this.rtcManager) {
|
|
393
|
+
this.logger.log("Unable to accept streams, no rtc manager");
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const shouldAcceptNewClients = (_b = (_a = this.rtcManager).shouldAcceptStreamsFromBothSides) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
397
|
+
const activeBreakout = false;
|
|
398
|
+
const myselfBroadcasting = false;
|
|
399
|
+
remoteParticipants.forEach((participant) => {
|
|
400
|
+
const { id: participantId, streams, newJoiner } = participant;
|
|
401
|
+
streams.forEach((stream) => {
|
|
402
|
+
var _a, _b;
|
|
403
|
+
const { id: streamId, state: streamState } = stream;
|
|
404
|
+
let newState = undefined;
|
|
405
|
+
const isInSameRoomOrGroupOrClientBroadcasting = true;
|
|
406
|
+
if (isInSameRoomOrGroupOrClientBroadcasting) {
|
|
407
|
+
if (streamState !== "done_accept") {
|
|
408
|
+
newState = `${newJoiner && streamId === "0" ? "new" : "to"}_accept`;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else if (myselfBroadcasting) {
|
|
412
|
+
if (streamState !== "done_accept") {
|
|
413
|
+
newState = `${newJoiner && streamId === "0" ? "done" : "old"}_accept`;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
if (streamState !== "done_unaccept") {
|
|
418
|
+
newState = "to_unaccept";
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (!newState) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (newState === "to_accept" ||
|
|
425
|
+
(newState === "new_accept" && shouldAcceptNewClients) ||
|
|
426
|
+
(newState === "old_accept" && !shouldAcceptNewClients)) {
|
|
427
|
+
this.logger.log(`Accepting stream ${streamId} from ${participantId}`);
|
|
428
|
+
(_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.acceptNewStream({
|
|
429
|
+
streamId: streamId === "0" ? participantId : streamId,
|
|
430
|
+
clientId: participantId,
|
|
431
|
+
shouldAddLocalVideo: streamId === "0",
|
|
432
|
+
activeBreakout,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
else if (newState === "new_accept" || newState === "old_accept") {
|
|
436
|
+
}
|
|
437
|
+
else if (newState === "to_unaccept") {
|
|
438
|
+
this.logger.log(`Disconnecting stream ${streamId} from ${participantId}`);
|
|
439
|
+
(_b = this.rtcManager) === null || _b === void 0 ? void 0 : _b.disconnect(streamId === "0" ? participantId : streamId, activeBreakout);
|
|
440
|
+
}
|
|
441
|
+
else if (newState !== "done_accept") {
|
|
442
|
+
this.logger.warn(`Stream state not handled: ${newState} for ${participantId}-${streamId}`);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
}
|
|
447
|
+
participant.updateStreamState(streamId, streamState.replace(/to_|new_|old_/, "done_"));
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
_handleStreamAdded(args) {
|
|
452
|
+
const streamAddedEvent = handleStreamAdded(this.remoteParticipants, args);
|
|
453
|
+
if (streamAddedEvent) {
|
|
454
|
+
this.dispatchEvent(streamAddedEvent);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
_joinRoom() {
|
|
458
|
+
this.signalSocket.emit("join_room", {
|
|
459
|
+
avatarUrl: null,
|
|
460
|
+
config: {
|
|
461
|
+
isAudioEnabled: this.localMedia.isMicrophoneEnabled(),
|
|
462
|
+
isVideoEnabled: this.localMedia.isCameraEnabled(),
|
|
463
|
+
},
|
|
464
|
+
deviceCapabilities: { canScreenshare: true },
|
|
465
|
+
displayName: this.displayName,
|
|
466
|
+
isCoLocated: false,
|
|
467
|
+
isDevicePermissionDenied: false,
|
|
468
|
+
kickFromOtherRooms: false,
|
|
469
|
+
organizationId: this.organizationId,
|
|
470
|
+
roomKey: this.roomKey,
|
|
471
|
+
roomName: this.roomName,
|
|
472
|
+
selfId: "",
|
|
473
|
+
userAgent: `browser-sdk:${sdkVersion || "unknown"}`,
|
|
474
|
+
externalId: this.externalId,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
join() {
|
|
478
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
479
|
+
if (["connected", "connecting"].includes(this.connectionStatus)) {
|
|
480
|
+
console.warn(`Trying to join when room state is already ${this.connectionStatus}`);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
this.signalSocket.connect();
|
|
484
|
+
this.connectionStatus = "connecting";
|
|
485
|
+
this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
|
|
486
|
+
detail: {
|
|
487
|
+
connectionStatus: this.connectionStatus,
|
|
488
|
+
},
|
|
489
|
+
}));
|
|
490
|
+
const organization = yield this.organizationServiceCache.fetchOrganization();
|
|
491
|
+
if (!organization) {
|
|
492
|
+
throw new Error("Invalid room url");
|
|
493
|
+
}
|
|
494
|
+
this.organizationId = organization.organizationId;
|
|
495
|
+
if (this._ownsLocalMedia) {
|
|
496
|
+
yield this.localMedia.start();
|
|
497
|
+
}
|
|
498
|
+
this._deviceCredentials = yield this.credentialsService.getCredentials();
|
|
499
|
+
this.logger.log("Connected to signal socket");
|
|
500
|
+
this.signalSocket.emit("identify_device", { deviceCredentials: this._deviceCredentials });
|
|
501
|
+
this.signalSocket.once("device_identified", () => {
|
|
502
|
+
this._joinRoom();
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
knock() {
|
|
507
|
+
this.connectionStatus = "knocking";
|
|
508
|
+
this.dispatchEvent(new RoomConnectionEvent("connection_status_changed", {
|
|
509
|
+
detail: {
|
|
510
|
+
connectionStatus: this.connectionStatus,
|
|
511
|
+
},
|
|
512
|
+
}));
|
|
513
|
+
this.signalSocket.emit("knock_room", {
|
|
514
|
+
displayName: this.displayName,
|
|
515
|
+
imageUrl: null,
|
|
516
|
+
kickFromOtherRooms: true,
|
|
517
|
+
liveVideo: false,
|
|
518
|
+
organizationId: this.organizationId,
|
|
519
|
+
roomKey: this._roomKey,
|
|
520
|
+
roomName: this.roomName,
|
|
521
|
+
externalId: this.externalId,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
leave() {
|
|
525
|
+
this.connectionStatus = "disconnecting";
|
|
526
|
+
if (this._ownsLocalMedia) {
|
|
527
|
+
this.localMedia.stop();
|
|
528
|
+
}
|
|
529
|
+
if (this.rtcManager) {
|
|
530
|
+
this.localMedia.removeRtcManager(this.rtcManager);
|
|
531
|
+
this.rtcManager.disconnectAll();
|
|
532
|
+
this.rtcManager = undefined;
|
|
533
|
+
}
|
|
534
|
+
if (!this.signalSocket) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
this.signalSocket.emit("leave_room");
|
|
538
|
+
this.signalSocket.disconnect();
|
|
539
|
+
this.connectionStatus = "disconnected";
|
|
540
|
+
}
|
|
541
|
+
sendChatMessage(text) {
|
|
542
|
+
this.signalSocket.emit("chat_message", {
|
|
543
|
+
text,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
setDisplayName(displayName) {
|
|
547
|
+
this.signalSocket.emit("send_client_metadata", {
|
|
548
|
+
type: "UserData",
|
|
549
|
+
payload: {
|
|
550
|
+
displayName,
|
|
551
|
+
},
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
acceptWaitingParticipant(participantId) {
|
|
555
|
+
this.signalSocket.emit("handle_knock", {
|
|
556
|
+
action: "accept",
|
|
557
|
+
clientId: participantId,
|
|
558
|
+
response: {},
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
rejectWaitingParticipant(participantId) {
|
|
562
|
+
this.signalSocket.emit("handle_knock", {
|
|
563
|
+
action: "reject",
|
|
564
|
+
clientId: participantId,
|
|
565
|
+
response: {},
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
updateStreamResolution({ streamId, width, height }) {
|
|
569
|
+
var _a, _b;
|
|
570
|
+
if (!streamId || !this.rtcManager) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (((_b = (_a = this.localParticipant) === null || _a === void 0 ? void 0 : _a.stream) === null || _b === void 0 ? void 0 : _b.id) === streamId) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const old = reportedStreamResolutions.get(streamId);
|
|
577
|
+
if (!old || old.width !== width || old.height !== height) {
|
|
578
|
+
this.rtcManager.updateStreamResolution(streamId, null, { width: width || 1, height: height || 1 });
|
|
579
|
+
}
|
|
580
|
+
reportedStreamResolutions.set(streamId, { width, height });
|
|
581
|
+
}
|
|
582
|
+
startScreenshare() {
|
|
583
|
+
var _a, _b;
|
|
584
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
585
|
+
const screenshareStream = this.localMedia.screenshareStream || (yield this.localMedia.startScreenshare());
|
|
586
|
+
const onEnded = () => {
|
|
587
|
+
this.stopScreenshare();
|
|
588
|
+
};
|
|
589
|
+
if ("oninactive" in screenshareStream) {
|
|
590
|
+
screenshareStream.addEventListener("inactive", onEnded);
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
(_a = screenshareStream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.addEventListener("ended", onEnded);
|
|
594
|
+
}
|
|
595
|
+
(_b = this.rtcManager) === null || _b === void 0 ? void 0 : _b.addNewStream(screenshareStream.id, screenshareStream, false, true);
|
|
596
|
+
this.screenshares = [
|
|
597
|
+
...this.screenshares,
|
|
598
|
+
{
|
|
599
|
+
participantId: this.selfId || "",
|
|
600
|
+
id: screenshareStream.id,
|
|
601
|
+
hasAudioTrack: false,
|
|
602
|
+
stream: screenshareStream,
|
|
603
|
+
isLocal: true,
|
|
604
|
+
},
|
|
605
|
+
];
|
|
606
|
+
this.dispatchEvent(new RoomConnectionEvent("screenshare_started", {
|
|
607
|
+
detail: {
|
|
608
|
+
participantId: this.selfId || "",
|
|
609
|
+
id: screenshareStream.id,
|
|
610
|
+
hasAudioTrack: false,
|
|
611
|
+
stream: screenshareStream,
|
|
612
|
+
isLocal: true,
|
|
613
|
+
},
|
|
614
|
+
}));
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
stopScreenshare() {
|
|
618
|
+
var _a;
|
|
619
|
+
if (this.localMedia.screenshareStream) {
|
|
620
|
+
const { id } = this.localMedia.screenshareStream;
|
|
621
|
+
(_a = this.rtcManager) === null || _a === void 0 ? void 0 : _a.removeStream(id, this.localMedia.screenshareStream, null);
|
|
622
|
+
this.screenshares = this.screenshares.filter((s) => s.id !== id);
|
|
623
|
+
this.dispatchEvent(new RoomConnectionEvent("screenshare_stopped", { detail: { participantId: this.selfId || "", id } }));
|
|
624
|
+
this.localMedia.stopScreenshare();
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
interface RoomParticipantData {
|
|
2
|
+
displayName: string;
|
|
3
|
+
id: string;
|
|
4
|
+
stream?: MediaStream;
|
|
5
|
+
isAudioEnabled: boolean;
|
|
6
|
+
isVideoEnabled: boolean;
|
|
7
|
+
}
|
|
8
|
+
export default class RoomParticipant {
|
|
9
|
+
readonly displayName: string;
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly stream?: MediaStream;
|
|
12
|
+
readonly isAudioEnabled: boolean;
|
|
13
|
+
readonly isLocalParticipant: boolean;
|
|
14
|
+
readonly isVideoEnabled: boolean;
|
|
15
|
+
constructor({ displayName, id, stream, isAudioEnabled, isVideoEnabled }: RoomParticipantData);
|
|
16
|
+
}
|
|
17
|
+
interface RemoteParticipantData {
|
|
18
|
+
newJoiner: boolean;
|
|
19
|
+
streams: string[];
|
|
20
|
+
}
|
|
21
|
+
export type StreamState = "new_accept" | "to_accept" | "old_accept" | "done_accept" | "to_unaccept" | "done_unaccept" | "auto";
|
|
22
|
+
interface Stream {
|
|
23
|
+
id: string;
|
|
24
|
+
state: StreamState;
|
|
25
|
+
}
|
|
26
|
+
export declare class RemoteParticipant extends RoomParticipant {
|
|
27
|
+
readonly newJoiner: boolean;
|
|
28
|
+
readonly streams: Stream[];
|
|
29
|
+
constructor({ displayName, id, newJoiner, streams, isAudioEnabled, isVideoEnabled, }: RoomParticipantData & RemoteParticipantData);
|
|
30
|
+
addStream(streamId: string, state: StreamState): void;
|
|
31
|
+
removeStream(streamId: string): void;
|
|
32
|
+
updateStreamState(streamId: string, state: StreamState): void;
|
|
33
|
+
}
|
|
34
|
+
export declare class LocalParticipant extends RoomParticipant {
|
|
35
|
+
readonly isLocalParticipant = true;
|
|
36
|
+
constructor({ displayName, id, stream, isAudioEnabled, isVideoEnabled }: RoomParticipantData);
|
|
37
|
+
}
|
|
38
|
+
export interface WaitingParticipant {
|
|
39
|
+
id: string;
|
|
40
|
+
displayName: string | null;
|
|
41
|
+
}
|
|
42
|
+
export declare class Screenshare {
|
|
43
|
+
readonly participantId: string;
|
|
44
|
+
readonly id: string;
|
|
45
|
+
readonly hasAudioTrack: boolean;
|
|
46
|
+
readonly stream?: MediaStream;
|
|
47
|
+
readonly isLocal: boolean;
|
|
48
|
+
constructor({ participantId, id, hasAudioTrack, stream, isLocal }: Screenshare);
|
|
49
|
+
}
|
|
50
|
+
export {};
|