@reactoo/watchtogether-sdk-js 2.5.35 → 2.5.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/watchtogether-sdk.js +21 -10
- package/dist/watchtogether-sdk.min.js +2 -2
- package/example/bulk_join_room/bulk_join_room.html +1 -0
- package/example/bulk_join_room/bulk_join_room_2.css +62 -0
- package/example/bulk_join_room/bulk_join_room_2.html +560 -0
- package/example/bulk_join_room/sound_2.mp3 +0 -0
- package/example/index.html +8 -5
- package/package.json +1 -1
- package/src/models/room-session.js +20 -43
- package/src/models/room.js +14 -16
- package/src/modules/wt-iot.js +14 -7
- package/src/modules/wt-room.js +87 -96
- package/src/modules/wt-utils.js +23 -1
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
<input type="number" id="connection-timeout" placeholder="5000" min="0">
|
|
33
33
|
|
|
34
34
|
<br><br>
|
|
35
|
+
|
|
35
36
|
<button type="submit" id="join-room">Create users and join room</button>
|
|
36
37
|
|
|
37
38
|
<button type="button" id="leave-room" onclick="leaveParticipants()">Leave room</button>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
.app-wrapper {
|
|
2
|
+
display: flex;
|
|
3
|
+
gap: 50px;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.settings,
|
|
7
|
+
.participants-constrols{
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
gap: 15px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.heading {
|
|
14
|
+
font-size: 20px;
|
|
15
|
+
font-weight: bold;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.form-group {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
gap: 2px;
|
|
22
|
+
width: 280px;
|
|
23
|
+
max-width: 100%;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.form-group input,
|
|
27
|
+
.form-group button,
|
|
28
|
+
.form-group textarea {
|
|
29
|
+
width: 100%;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
resize: vertical;
|
|
32
|
+
margin: unset;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.form-group input[type="checkbox"] {
|
|
36
|
+
width: 14px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#participants-list {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: row;
|
|
42
|
+
flex-wrap: wrap;
|
|
43
|
+
gap: 50px;
|
|
44
|
+
margin-top: 10px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.participant-container {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
border: 1px solid black;
|
|
51
|
+
width: 150px;
|
|
52
|
+
gap: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.participant-container canvas {
|
|
56
|
+
aspect-ratio: calc(16/9);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.space {
|
|
60
|
+
height: 10px;
|
|
61
|
+
background-color: darkgrey;
|
|
62
|
+
}
|
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>The Bulk Join 2</title>
|
|
7
|
+
<script src="../../dist/watchtogether-sdk.js"></script>
|
|
8
|
+
<link rel="stylesheet" href="./bulk_join_room_2.css">
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<div class="app-wrapper">
|
|
13
|
+
<div class="settings">
|
|
14
|
+
<div class="heading">Settings</div>
|
|
15
|
+
|
|
16
|
+
<div class="form-group">
|
|
17
|
+
<label for="participants-count">Participants count</label>
|
|
18
|
+
<input type="number" id="participants-count" onchange="applySettingsFromForm()">
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="form-group">
|
|
22
|
+
<label for="participants-order-randomization">Participants order randomization</label>
|
|
23
|
+
<input type="checkbox" id="participants-order-randomization" onchange="applySettingsFromForm()">
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="form-group">
|
|
27
|
+
<label for="room-id">Room ID</label>
|
|
28
|
+
<input type="text" id="room-id" onchange="applySettingsFromForm()">
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="form-group">
|
|
32
|
+
<label for="pin-hash">Pin hash</label>
|
|
33
|
+
<input type="text" id="pin-hash" placeholder="Not required" onchange="applySettingsFromForm()">
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="form-group">
|
|
37
|
+
<label for="room-id">Connection delay (seconds)</label>
|
|
38
|
+
<input type="number" id="connection-delay" min="0" step="0.25" onchange="applySettingsFromForm()">
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="form-group">
|
|
42
|
+
<label for="parallel-queues">Parallel connection queues</label>
|
|
43
|
+
<input type="number" id="parallel-queues" min="1" step="4" onchange="applySettingsFromForm()">
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="form-group">
|
|
47
|
+
<label for="instance-type">Instance type</label>
|
|
48
|
+
<input type="text" id="instance-type" onchange="applySettingsFromForm()">
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="form-group">
|
|
52
|
+
<label for="fetch-names">Fetch participants names</label>
|
|
53
|
+
<input type="checkbox" id="fetch-names" onchange="applySettingsFromForm()">
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="form-group">
|
|
57
|
+
<button type="button" onclick="resetSettings()">Reset settings</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="participants-constrols">
|
|
62
|
+
<div class="heading">Participants controls</div>
|
|
63
|
+
<div class="form-group">
|
|
64
|
+
<button type="button" onclick="createParticipants()">Create participants (without connect)</button>
|
|
65
|
+
<button type="button" onclick="destroyParticipants()">Destroy participants</button>
|
|
66
|
+
<br>
|
|
67
|
+
<button type="button" onclick="createSingleParticipant()">Create single participant (without connect)</button>
|
|
68
|
+
<br>
|
|
69
|
+
<button type="button" onclick="joinParticipantsToRoom()">Join all participants to room</button>
|
|
70
|
+
<button type="button" onclick="disconnectParticipantsFromRoom()">Disconnect all participants from room</button>
|
|
71
|
+
<br>
|
|
72
|
+
<div class="form-group">
|
|
73
|
+
<label for="participants-names">Participants names (one name per line)</label>
|
|
74
|
+
<textarea id="participants-names" rows="5" onchange="applySettingsFromForm()"></textarea>
|
|
75
|
+
</div>
|
|
76
|
+
<button type="button" onclick="setParticipantsNames()">Set Participants Names</button>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div class="participants-list-container">
|
|
81
|
+
<div class="heading">Participants</div>
|
|
82
|
+
<div id="participants-list"></div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<script>
|
|
87
|
+
// https://github.com/w3c/webcodecs/blob/f73dd2ccea32c0652ef94d48b87d728e4f709569/samples/image-decoder/animated-gif-renderer.html
|
|
88
|
+
|
|
89
|
+
// ====================== Configuration ===================================
|
|
90
|
+
|
|
91
|
+
const settingsUrlParameterName = 'settings';
|
|
92
|
+
|
|
93
|
+
const defaultSettings = {
|
|
94
|
+
participantsCount: 5,
|
|
95
|
+
participantsOrderRandomization: false,
|
|
96
|
+
minParticipantsCount: 1,
|
|
97
|
+
maxParticipantsCount: 32,
|
|
98
|
+
instanceType: "reactooDemo",
|
|
99
|
+
roomId: "",
|
|
100
|
+
pinHash: "",
|
|
101
|
+
connectionDelay: 1,
|
|
102
|
+
parallelQueues: 1, // number of parallel queues for joining participants - only applied when connectionDelay is 0
|
|
103
|
+
fetchNames: false,
|
|
104
|
+
participantsNames: [],
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const gifsCount = 32; // Number of gifs in ./persons_gifs folder
|
|
108
|
+
|
|
109
|
+
// ====================== General Variables ===============================
|
|
110
|
+
|
|
111
|
+
let settings;
|
|
112
|
+
|
|
113
|
+
const elements = {
|
|
114
|
+
form: {
|
|
115
|
+
participantsCount: document.getElementById("participants-count"),
|
|
116
|
+
participantsOrderRandomization: document.getElementById("participants-order-randomization"),
|
|
117
|
+
roomId: document.getElementById("room-id"),
|
|
118
|
+
pinHash: document.getElementById("pin-hash"),
|
|
119
|
+
connectionDelay: document.getElementById("connection-delay"),
|
|
120
|
+
parallelQueues: document.getElementById("parallel-queues"),
|
|
121
|
+
instanceType: document.getElementById("instance-type"),
|
|
122
|
+
fetchNames: document.getElementById("fetch-names"),
|
|
123
|
+
participantsNames: document.getElementById("participants-names"),
|
|
124
|
+
},
|
|
125
|
+
participantsList: document.getElementById("participants-list"),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const participants = new Array(gifsCount).fill(undefined);
|
|
129
|
+
|
|
130
|
+
const usedGifsIds = [];
|
|
131
|
+
|
|
132
|
+
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
133
|
+
|
|
134
|
+
// ====================== Helper Functions ================================
|
|
135
|
+
|
|
136
|
+
function getUrlParameter(varName){
|
|
137
|
+
const queryStr = decodeURI(window.location.search) + '&';
|
|
138
|
+
const regex = new RegExp('.*?[&\\?]' + varName + '=(.*?)&.*');
|
|
139
|
+
const val = queryStr.replace(regex, "$1");
|
|
140
|
+
return val === queryStr ? false : val;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ====================== Settings Functions ==============================
|
|
144
|
+
|
|
145
|
+
function applySettingsFromForm() {
|
|
146
|
+
settings = {
|
|
147
|
+
participantsCount: parseInt(elements.form.participantsCount.value) || 0,
|
|
148
|
+
participantsOrderRandomization: elements.form.participantsOrderRandomization.checked,
|
|
149
|
+
minParticipantsCount: parseInt(settings.minParticipantsCount) || 1,
|
|
150
|
+
maxParticipantsCount: parseInt(settings.maxParticipantsCount) || 32,
|
|
151
|
+
roomId: elements.form.roomId.value || "",
|
|
152
|
+
pinHash: elements.form.pinHash.value || "",
|
|
153
|
+
connectionDelay: parseFloat(elements.form.connectionDelay.value) || 0,
|
|
154
|
+
parallelQueues: parseInt(elements.form.parallelQueues.value) || 1,
|
|
155
|
+
instanceType: elements.form.instanceType.value || "reactooDemo",
|
|
156
|
+
fetchNames: elements.form.fetchNames.checked,
|
|
157
|
+
participantsNames: elements.form.participantsNames.value.split("\n"),
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
setSettingsToUrl(false);
|
|
161
|
+
fillSettingsForm();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function setSettingsToUrl(deleteInstead = false) {
|
|
165
|
+
const url = new URL(location.href);
|
|
166
|
+
if (deleteInstead) {
|
|
167
|
+
url.searchParams.delete(settingsUrlParameterName);
|
|
168
|
+
} else {
|
|
169
|
+
url.searchParams.set(settingsUrlParameterName, JSON.stringify(settings));
|
|
170
|
+
}
|
|
171
|
+
history.replaceState (null, '', url);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function getSettingsFromUrl() {
|
|
175
|
+
let settingsFromUrl = getUrlParameter(settingsUrlParameterName);
|
|
176
|
+
|
|
177
|
+
if (settingsFromUrl && typeof settingsFromUrl === 'string') {
|
|
178
|
+
settingsFromUrl = JSON.parse(decodeURIComponent(settingsFromUrl.replace(/\+/g, ' ')));
|
|
179
|
+
|
|
180
|
+
if (settingsFromUrl && typeof settingsFromUrl === 'object' && !Array.isArray(settingsFromUrl)) {
|
|
181
|
+
settings = {...settings, ...settingsFromUrl};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function initializeSettings() {
|
|
187
|
+
settings = {...defaultSettings};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function resetSettings() {
|
|
191
|
+
initializeSettings();
|
|
192
|
+
setSettingsToUrl(true);
|
|
193
|
+
fillSettingsForm();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function fillSettingsForm() {
|
|
197
|
+
elements.form.participantsCount.value = settings.participantsCount;
|
|
198
|
+
elements.form.participantsOrderRandomization.checked = settings.participantsOrderRandomization;
|
|
199
|
+
elements.form.roomId.value = settings.roomId;
|
|
200
|
+
elements.form.pinHash.value = settings.pinHash;
|
|
201
|
+
elements.form.connectionDelay.value = settings.connectionDelay;
|
|
202
|
+
elements.form.parallelQueues.value = settings.parallelQueues;
|
|
203
|
+
elements.form.instanceType.value = settings.instanceType;
|
|
204
|
+
elements.form.fetchNames.checked = settings.fetchNames;
|
|
205
|
+
elements.form.participantsNames.value = settings.participantsNames.join("\n");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ====================== Main Logic ==================================
|
|
209
|
+
|
|
210
|
+
function getRandomNotUsedGifId() {
|
|
211
|
+
const availableGifsIds = new Array(gifsCount).fill().map((_, index) => index+1).filter(e => !usedGifsIds.includes(e));
|
|
212
|
+
let randomNotUsedGifId;
|
|
213
|
+
|
|
214
|
+
if (settings.participantsOrderRandomization) {
|
|
215
|
+
randomNotUsedGifId = availableGifsIds[Math.floor(Math.random() * availableGifsIds.length)];
|
|
216
|
+
} else {
|
|
217
|
+
randomNotUsedGifId = Math.min(...availableGifsIds);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (randomNotUsedGifId) {
|
|
221
|
+
usedGifsIds.push(randomNotUsedGifId);
|
|
222
|
+
return randomNotUsedGifId;
|
|
223
|
+
} else {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function createParticipant(participantOrder) {
|
|
229
|
+
const gifId = getRandomNotUsedGifId();
|
|
230
|
+
|
|
231
|
+
if (gifId === null) {
|
|
232
|
+
return Promise.reject();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const gifUrl = `./persons_gifs/${gifId}.gif`;
|
|
236
|
+
const frameIndex = 0;
|
|
237
|
+
const sdkInstance = WatchTogetherSDK({debug:true, storagePrefix: `participant_${gifId}`})({instanceType: settings.instanceType});
|
|
238
|
+
|
|
239
|
+
const containerElement = document.createElement('div');
|
|
240
|
+
containerElement.classList.add('participant-container');
|
|
241
|
+
containerElement.setAttribute('data-participant-id', gifId);
|
|
242
|
+
containerElement.style.order = participantOrder;
|
|
243
|
+
|
|
244
|
+
const canvasElement = document.createElement('canvas');
|
|
245
|
+
canvasElement.setAttribute('title', `Gif ID - ` + gifId);
|
|
246
|
+
const canvasContext = canvasElement.getContext('2d');
|
|
247
|
+
containerElement.appendChild(canvasElement);
|
|
248
|
+
|
|
249
|
+
let spaceElement = document.createElement('div');
|
|
250
|
+
spaceElement.className = 'space';
|
|
251
|
+
containerElement.appendChild(spaceElement);
|
|
252
|
+
|
|
253
|
+
let participantNameElement;
|
|
254
|
+
participantNameElement = document.createElement('input');
|
|
255
|
+
participantNameElement.setAttribute('type', 'text');
|
|
256
|
+
participantNameElement.setAttribute('placeholder', 'Name');
|
|
257
|
+
containerElement.appendChild(participantNameElement);
|
|
258
|
+
|
|
259
|
+
const getNameButtonElement = document.createElement('button');
|
|
260
|
+
getNameButtonElement.innerText = 'Get name';
|
|
261
|
+
getNameButtonElement.addEventListener('click', () => {
|
|
262
|
+
sdkInstance.user.getUserSelf()
|
|
263
|
+
.then(response => {
|
|
264
|
+
participants[gifId].displayname = response.data.displayname;
|
|
265
|
+
participantNameElement.value = response.data.displayname;
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
containerElement.appendChild(getNameButtonElement);
|
|
269
|
+
|
|
270
|
+
const setNameButtonElement = document.createElement('button');
|
|
271
|
+
setNameButtonElement.innerText = 'Save name';
|
|
272
|
+
setNameButtonElement.addEventListener('click', () => sdkInstance.user.updateUserSelf({lastRoomId: settings.roomId, displayname: participantNameElement.value}));
|
|
273
|
+
containerElement.appendChild(setNameButtonElement);
|
|
274
|
+
|
|
275
|
+
spaceElement = document.createElement('div');
|
|
276
|
+
spaceElement.className = 'space';
|
|
277
|
+
containerElement.appendChild(spaceElement);
|
|
278
|
+
|
|
279
|
+
const destroyButtonElement = document.createElement('button');
|
|
280
|
+
destroyButtonElement.innerText = 'Destroy';
|
|
281
|
+
destroyButtonElement.addEventListener('click', () => destroyParticipant(participants[gifId]));
|
|
282
|
+
containerElement.appendChild(destroyButtonElement);
|
|
283
|
+
|
|
284
|
+
const joinButtonElement = document.createElement('button');
|
|
285
|
+
joinButtonElement.innerText = 'Join';
|
|
286
|
+
joinButtonElement.addEventListener('click', () => joinParticipantToRoom(participants[gifId]));
|
|
287
|
+
containerElement.appendChild(joinButtonElement);
|
|
288
|
+
|
|
289
|
+
const leaveButtonElement = document.createElement('button');
|
|
290
|
+
leaveButtonElement.innerText = 'Leave';
|
|
291
|
+
leaveButtonElement.addEventListener('click', () => disconnectParticipantFromRoom(participants[gifId]));
|
|
292
|
+
containerElement.appendChild(leaveButtonElement);
|
|
293
|
+
|
|
294
|
+
spaceElement = document.createElement('div');
|
|
295
|
+
spaceElement.className = 'space';
|
|
296
|
+
containerElement.appendChild(spaceElement);
|
|
297
|
+
|
|
298
|
+
const toggleCameraButtonElement = document.createElement('button');
|
|
299
|
+
toggleCameraButtonElement.innerText = 'Toggle camera';
|
|
300
|
+
toggleCameraButtonElement.addEventListener('click', () => participants[gifId].session.toggleVideo());
|
|
301
|
+
containerElement.appendChild(toggleCameraButtonElement);
|
|
302
|
+
|
|
303
|
+
const toggleAudioButtonElement = document.createElement('button');
|
|
304
|
+
toggleAudioButtonElement.innerText = 'Toggle audio';
|
|
305
|
+
toggleAudioButtonElement.addEventListener('click', () => participants[gifId].session.toggleAudio());
|
|
306
|
+
containerElement.appendChild(toggleAudioButtonElement);
|
|
307
|
+
|
|
308
|
+
const toggleRaiseHandButtonElement = document.createElement('button');
|
|
309
|
+
toggleRaiseHandButtonElement.innerText = 'Toggle raise hand';
|
|
310
|
+
toggleRaiseHandButtonElement.addEventListener('click', () => {
|
|
311
|
+
sdkInstance.room.setUser({roomId: settings.roomId, userId: participants[gifId].sdkInstance.userId, flag: participants[gifId].handRaised ? 'handLower' : 'handRaise'});
|
|
312
|
+
participants[gifId].handRaised = !participants[gifId].handRaised;
|
|
313
|
+
});
|
|
314
|
+
containerElement.appendChild(toggleRaiseHandButtonElement);
|
|
315
|
+
|
|
316
|
+
return fetch(gifUrl)
|
|
317
|
+
.then(response => {
|
|
318
|
+
const imageDecoder = new ImageDecoder({data: response.body, type: 'image/gif'});
|
|
319
|
+
|
|
320
|
+
const participant = {
|
|
321
|
+
gifId,
|
|
322
|
+
gifUrl,
|
|
323
|
+
imageDecoder,
|
|
324
|
+
frameIndex,
|
|
325
|
+
reverseOrder: false, // boomerang animation
|
|
326
|
+
width: null, // Will be set after first frame is decoded
|
|
327
|
+
height: null, // Will be set after first frame is decoded
|
|
328
|
+
canvas: canvasElement,
|
|
329
|
+
canvasContext,
|
|
330
|
+
sdkInstance,
|
|
331
|
+
loginPromise: sdkInstance.auth.deviceLogin(gifId),
|
|
332
|
+
session: null, // Will be set after room join
|
|
333
|
+
handRaised: false,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
if (settings.fetchNames) {
|
|
337
|
+
participant.loginPromise.then(() => sdkInstance.user.getUserSelf())
|
|
338
|
+
.then(response => {
|
|
339
|
+
participant.displayname = response.data.displayname;
|
|
340
|
+
participantNameElement.value = response.data.displayname;
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return imageDecoder
|
|
345
|
+
.decode({frameIndex})
|
|
346
|
+
.then(decodeResult => {
|
|
347
|
+
participant.width = decodeResult.image.displayWidth;
|
|
348
|
+
participant.canvas.width = decodeResult.image.displayWidth;
|
|
349
|
+
|
|
350
|
+
participant.height = decodeResult.image.displayHeight;
|
|
351
|
+
participant.canvas.height = decodeResult.image.displayHeight;
|
|
352
|
+
|
|
353
|
+
participant.canvas.id = `canvas-${participant.gifId}`;
|
|
354
|
+
|
|
355
|
+
// Start render loop
|
|
356
|
+
renderGif(decodeResult, participant);
|
|
357
|
+
|
|
358
|
+
elements.participantsList.appendChild(containerElement);
|
|
359
|
+
|
|
360
|
+
return participant;
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function createParticipants(participantsCount = settings.participantsCount) {
|
|
366
|
+
destroyParticipants()
|
|
367
|
+
.then(() => {
|
|
368
|
+
for (let i = 0; i < participantsCount; i++) {
|
|
369
|
+
createParticipant(i).then(participant => participants[participant.gifId] = participant);
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function createSingleParticipant() {
|
|
375
|
+
const participantOrder = participants.filter(p => p).length;
|
|
376
|
+
|
|
377
|
+
console.log(participantOrder);
|
|
378
|
+
|
|
379
|
+
createParticipant(participantOrder)
|
|
380
|
+
.then(participant => participants[participant.gifId] = participant);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function destroyParticipant(participant) {
|
|
384
|
+
disconnectParticipantFromRoom(participant)
|
|
385
|
+
.then(() => {
|
|
386
|
+
participant.imageDecoder.close();
|
|
387
|
+
participant.sdkInstance.auth.logout();
|
|
388
|
+
usedGifsIds.splice(usedGifsIds.indexOf(participant.gifId), 1);
|
|
389
|
+
document.querySelector('.participant-container[data-participant-id="' + participant.gifId + '"]').remove();
|
|
390
|
+
|
|
391
|
+
participants[participant.gifId] = undefined;
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function destroyParticipants() {
|
|
396
|
+
return disconnectParticipantsFromRoom()
|
|
397
|
+
.then(() => {
|
|
398
|
+
participants.filter(p => p).forEach(participant => {
|
|
399
|
+
participant.imageDecoder.close();
|
|
400
|
+
participant.sdkInstance.auth.logout();
|
|
401
|
+
usedGifsIds.splice(usedGifsIds.indexOf(participant.gifId), 1);
|
|
402
|
+
document.querySelector('.participant-container[data-participant-id="' + participant.gifId + '"]').remove();
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
for (let i = 0 ; i < participants.length ; i++) {
|
|
406
|
+
if (participants[i]) {
|
|
407
|
+
participants[i] = undefined;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function joinParticipantToRoom(participant) {
|
|
414
|
+
return participant.loginPromise.then(() => {
|
|
415
|
+
participant.sdkInstance.room.createSession({roomId: settings.roomId, pinHash: settings.pinHash})
|
|
416
|
+
.then(session => {
|
|
417
|
+
participant.session = session;
|
|
418
|
+
return Promise.all([session, session.connect()])
|
|
419
|
+
})
|
|
420
|
+
.then(([session, _]) => {
|
|
421
|
+
return getAudioStream()
|
|
422
|
+
.then(audioStream => {
|
|
423
|
+
participant.stream = participant.canvas.captureStream();
|
|
424
|
+
participant.stream.addTrack(audioStream.getAudioTracks()[0]);
|
|
425
|
+
return session.publishLocal(participant.stream, {getStreamIfEmpty: false});
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function joinParticipantsToRoom () {
|
|
432
|
+
if (settings.connectionDelay === 0 && settings.parallelQueues > 0) {
|
|
433
|
+
// Parallel join
|
|
434
|
+
|
|
435
|
+
const parallelQueues = new Array(participants.filter(p => p).length).fill().map(() => Promise.resolve());
|
|
436
|
+
|
|
437
|
+
for (let i = 0 ; i < participants.filter(p => p).length ; i++) {
|
|
438
|
+
const parallelQueueIndex = i % settings.parallelQueues;
|
|
439
|
+
parallelQueues[parallelQueueIndex] = parallelQueues[parallelQueueIndex]
|
|
440
|
+
.then(() => joinParticipantToRoom(participants.filter(p => p)[i]))
|
|
441
|
+
.then(() => new Promise(resolve => setTimeout(resolve, settings.connectionDelay * 1000)));
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
// Serial join
|
|
445
|
+
|
|
446
|
+
return participants.filter(p => p).reduce((promiseChain, participant) => {
|
|
447
|
+
return promiseChain.then(() => joinParticipantToRoom(participant))
|
|
448
|
+
.then(() => new Promise(resolve => setTimeout(resolve, settings.connectionDelay * 1000)));
|
|
449
|
+
}, Promise.resolve());
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function disconnectParticipantFromRoom(participant) {
|
|
454
|
+
if (participant && participant.loginPromise) {
|
|
455
|
+
return participant.loginPromise
|
|
456
|
+
.then(() => {
|
|
457
|
+
if (participant.session && participant.session.disconnect) {
|
|
458
|
+
return participant.session.disconnect();
|
|
459
|
+
}
|
|
460
|
+
})
|
|
461
|
+
.then(() => participant.session = null);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function disconnectParticipantsFromRoom() {
|
|
466
|
+
return Promise.all(participants.filter(p => p).map(participant => disconnectParticipantFromRoom(participant)));
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function renderGif(decodeResult, participant) {
|
|
470
|
+
participant.canvasContext.drawImage(decodeResult.image, 0, 0);
|
|
471
|
+
|
|
472
|
+
const track = participant.imageDecoder.tracks.selectedTrack;
|
|
473
|
+
|
|
474
|
+
if (!track) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// We check complete here since `frameCount` won't be stable until all data
|
|
479
|
+
// has been received. This may cause us to receive a RangeError during the
|
|
480
|
+
// decode() call below which needs to be handled.
|
|
481
|
+
if (participant.imageDecoder.complete) {
|
|
482
|
+
if (track.frameCount == 1) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if ((participant.reverseOrder && participant.frameIndex - 1 <= 0) || (!participant.reverseOrder && participant.frameIndex + 1 >= track.frameCount)) {
|
|
487
|
+
participant.reverseOrder = !participant.reverseOrder;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (participant.reverseOrder) {
|
|
492
|
+
--participant.frameIndex;
|
|
493
|
+
} else {
|
|
494
|
+
++participant.frameIndex;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Decode the next frame ahead of display so it's ready in time.
|
|
498
|
+
participant.imageDecoder.decode({frameIndex: participant.frameIndex})
|
|
499
|
+
.then( nextDecodeResult => setTimeout(_ => {
|
|
500
|
+
renderGif(nextDecodeResult, participant);
|
|
501
|
+
}, decodeResult.image.duration / 1000.0))
|
|
502
|
+
.catch(e => {
|
|
503
|
+
// We can end up requesting an imageIndex past the end since we're using
|
|
504
|
+
// a ReadableStrem from fetch(), when this happens just wrap around.
|
|
505
|
+
if (e instanceof RangeError) {
|
|
506
|
+
participant.frameIndex = 0;
|
|
507
|
+
participant.imageDecoder.decode({frameIndex: imageIndex}).then(decodeResult => renderGif(decodeResult, participant));
|
|
508
|
+
} else {
|
|
509
|
+
throw e;
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function setParticipantsNames() {
|
|
515
|
+
participants.filter(p => p).forEach((participant, participantOrder) => {
|
|
516
|
+
if (settings.participantsNames[participantOrder]) {
|
|
517
|
+
participant.sdkInstance.user.updateUserSelf({lastRoomId: settings.roomId, displayname: settings.participantsNames[participantOrder]});
|
|
518
|
+
} else {
|
|
519
|
+
console.log('Not enough names');
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function getAudioStream() {
|
|
525
|
+
return new Promise(resolve => {
|
|
526
|
+
const request = new XMLHttpRequest();
|
|
527
|
+
// https://ttsmp3.com/
|
|
528
|
+
request.open('GET', './sound_2.mp3', true);
|
|
529
|
+
request.responseType = 'arraybuffer';
|
|
530
|
+
request.onload = function() {
|
|
531
|
+
audioContext.decodeAudioData(request.response)
|
|
532
|
+
.then(buffer => {
|
|
533
|
+
const streamDestination = audioContext.createMediaStreamDestination();
|
|
534
|
+
const source = audioContext.createBufferSource();
|
|
535
|
+
source.buffer = buffer;
|
|
536
|
+
source.connect(streamDestination);
|
|
537
|
+
source.loop = true;
|
|
538
|
+
source.start();
|
|
539
|
+
|
|
540
|
+
new Audio().srcObject = streamDestination.stream;
|
|
541
|
+
|
|
542
|
+
resolve(streamDestination.stream);
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
request.send();
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// ====================== Initialization ==============================
|
|
550
|
+
|
|
551
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
552
|
+
initializeSettings();
|
|
553
|
+
getSettingsFromUrl();
|
|
554
|
+
fillSettingsForm();
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
</script>
|
|
558
|
+
|
|
559
|
+
</body>
|
|
560
|
+
</html>
|
|
Binary file
|
package/example/index.html
CHANGED
|
@@ -97,13 +97,13 @@
|
|
|
97
97
|
Instance.room.getSessionByConstructId(constructId).toggleAudio()
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
let Instance = WatchTogetherSDK({debug:true})({instanceType:'reactooDemo'});
|
|
100
|
+
let Instance = WatchTogetherSDK({debug:true, apiUrl: 'https://api.reactoo.com/dev3/swagger.json'})({instanceType:'reactooDemo'});
|
|
101
101
|
|
|
102
102
|
Instance.auth.$on('login', () => {
|
|
103
103
|
console.log('We are in');
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
Instance.iot.$on('
|
|
106
|
+
Instance.iot.$on('connect', () => {
|
|
107
107
|
console.log('Iot connected');
|
|
108
108
|
});
|
|
109
109
|
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
console.log('Iot error' ,e);
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
-
Instance.iot.$on('
|
|
114
|
+
Instance.iot.$on('close', () => {
|
|
115
115
|
console.log('Iot disconnected');
|
|
116
116
|
});
|
|
117
117
|
|
|
@@ -125,7 +125,10 @@
|
|
|
125
125
|
|
|
126
126
|
if(roomId) {
|
|
127
127
|
console.log(roomId);
|
|
128
|
-
return
|
|
128
|
+
return Instance.room.getRoomById(roomId, pinHash).then((r) => {
|
|
129
|
+
Instance.iot?.subscribe(r.data.iotTopic);
|
|
130
|
+
return {roomId, pinHash}
|
|
131
|
+
})
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
// get our room or create one if it doesn't exist yet
|
|
@@ -148,7 +151,7 @@
|
|
|
148
151
|
})
|
|
149
152
|
|
|
150
153
|
})
|
|
151
|
-
.then(r => Instance.room.createSession({constructId, roomId:r.roomId, pinHash: r.pinHash, options: {
|
|
154
|
+
.then(r => Instance.room.createSession({constructId, roomId:r.roomId, pinHash: r.pinHash, role:'participant', options: {
|
|
152
155
|
//subscriptionRules: {participant: {videoWall: [], watchTogether: []}}
|
|
153
156
|
}})) // pin hash is not needed if you're owner of the room
|
|
154
157
|
.then(session => {
|