@reactoo/watchtogether-sdk-js 2.6.75 → 2.6.76

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.
@@ -2,733 +2,789 @@
2
2
  <html lang="en">
3
3
 
4
4
  <head>
5
- <meta charset="utf-8">
6
- <title>The Bulk Join 2</title>
5
+ <meta charset="utf-8">
6
+ <title>The Bulk Join 2</title>
7
7
  <script src="../../dist/watchtogether-sdk.js"></script>
8
- <link rel="stylesheet" href="./bulk_join_room_2.css">
9
-
10
- <style>.--high-quality{display:none}</style>
8
+ <link rel="stylesheet" href="./bulk_join_room_2.css">
11
9
  </head>
12
10
 
13
11
  <body>
14
12
  <div class="app-wrapper">
15
- <div class="settings">
16
- <div class="heading">Settings</div>
17
-
18
- <div class="form-group">
19
- <label for="participants-count">Participants count</label>
20
- <input type="number" id="participants-count" onchange="applySettingsFromForm()">
21
- </div>
22
-
23
- <div class="form-group --advanced --high-quality">
24
- <label for="participants-high-quality">High quality</label>
25
- <input type="checkbox" id="participants-high-quality" onchange="applySettingsFromForm()">
26
- </div>
27
-
28
- <div class="form-group --advanced">
29
- <label for="participants-order-randomization">Participants order randomization</label>
30
- <input type="checkbox" id="participants-order-randomization" onchange="applySettingsFromForm()">
31
- </div>
32
-
33
-
34
- <div class="form-group">
35
- <label for="room-id">Room ID</label>
36
- <input type="text" id="room-id" onchange="applySettingsFromForm()">
37
- </div>
38
-
39
- <div class="form-group --advanced">
40
- <label for="pin-hash">Pin hash</label>
41
- <input type="text" id="pin-hash" placeholder="Not required" onchange="applySettingsFromForm()">
42
- </div>
43
-
44
- <div class="form-group --advanced">
45
- <label for="room-id">Connection delay (seconds)</label>
46
- <input type="number" id="connection-delay" min="0" step="0.25" onchange="applySettingsFromForm()">
47
- </div>
48
-
49
- <div class="form-group --advanced">
50
- <label for="parallel-queues">Parallel connection queues</label>
51
- <input type="number" id="parallel-queues" min="1" step="4" onchange="applySettingsFromForm()">
52
- </div>
53
-
54
- <div class="form-group --advanced">
55
- <label for="instance-type">Instance type</label>
56
- <input type="text" id="instance-type" onchange="applySettingsFromForm()">
57
- </div>
58
-
59
- <div class="form-group --advanced">
60
- <label for="swagger-url">Swagger URL</label>
61
- <input type="text" id="swagger-url" onchange="applySettingsFromForm()">
62
- </div>
63
-
64
- <div class="form-group --advanced">
65
- <label for="fetch-names">Fetch participants names</label>
66
- <input type="checkbox" id="fetch-names" onchange="applySettingsFromForm()">
67
- </div>
68
-
69
- <div class="form-group">
70
- <button type="button" class="--advanced" onclick="resetSettings()">Reset settings</button>
71
- <button type="button" class="--high-quality" onclick="toggleAdvancedSettings()">Toggle advanced settings</button>
72
- </div>
73
-
74
- </div>
75
-
76
- <div class="participants-constrols">
77
- <div class="heading">Participants controls</div>
78
- <div class="form-group">
79
- <button type="button" onclick="createParticipants()">Create participants (without connect)</button>
80
- <button type="button" onclick="destroyParticipants()">Destroy participants</button>
81
- <br class="--advanced">
82
- <button type="button" class="--advanced" onclick="createSingleParticipant()">Create single participant (without connect)</button>
83
- <br>
84
- <button type="button" onclick="joinParticipantsToRoom()">Join all participants to room</button>
85
- <button type="button" onclick="disconnectParticipantsFromRoom()">Disconnect all participants from room</button>
86
- <br class="--advanced">
87
- <div class="form-group --advanced">
88
- <label for="participants-names">Participants names (one name per line)</label>
89
- <textarea id="participants-names" rows="5" onchange="applySettingsFromForm()"></textarea>
90
- </div>
91
- <button type="button" class="--advanced" onclick="setParticipantsNames()">Set Participants Names</button>
92
- </div>
93
-
94
- <div class="heading --advanced">Room controls</div>
95
- <div class="form-group --advanced">
96
- <button type="button" onclick="initiazeFakeChat()">Initiate fake chat</button>
97
- </div>
98
- </div>
99
-
100
- <div class="participants-list-container">
101
- <div class="heading">Participants</div>
102
- <div id="participants-list"></div>
103
- </div>
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 --advanced --high-quality">
22
+ <label for="participants-high-quality">High quality</label>
23
+ <input type="checkbox" id="participants-high-quality" onchange="applySettingsFromForm()">
24
+ </div>
25
+
26
+ <div class="form-group --advanced">
27
+ <label for="participants-order-randomization">Participants order randomization</label>
28
+ <input type="checkbox" id="participants-order-randomization" onchange="applySettingsFromForm()">
29
+ </div>
30
+
31
+
32
+ <div class="form-group">
33
+ <label for="room-id">Room ID</label>
34
+ <input type="text" id="room-id" onchange="applySettingsFromForm()">
35
+ </div>
36
+
37
+ <div class="form-group --advanced">
38
+ <label for="pin-hash">Pin hash</label>
39
+ <input type="text" id="pin-hash" placeholder="Not required" onchange="applySettingsFromForm()">
40
+ </div>
41
+
42
+ <div class="form-group --advanced">
43
+ <label for="room-id">Connection delay (seconds)</label>
44
+ <input type="number" id="connection-delay" min="0" step="0.25" onchange="applySettingsFromForm()">
45
+ </div>
46
+
47
+ <div class="form-group --advanced">
48
+ <label for="parallel-queues">Parallel connection queues</label>
49
+ <input type="number" id="parallel-queues" min="1" step="4" onchange="applySettingsFromForm()">
50
+ </div>
51
+
52
+ <div class="form-group --advanced">
53
+ <label for="instance-type">Instance type</label>
54
+ <input type="text" id="instance-type" onchange="applySettingsFromForm()">
55
+ </div>
56
+
57
+ <div class="form-group --advanced">
58
+ <label for="swagger-url">Swagger URL</label>
59
+ <input type="text" id="swagger-url" onchange="applySettingsFromForm()">
60
+ </div>
61
+
62
+ <div class="form-group --advanced">
63
+ <label for="fetch-names">Fetch participants names</label>
64
+ <input type="checkbox" id="fetch-names" onchange="applySettingsFromForm()">
65
+ </div>
66
+
67
+ <div class="form-group">
68
+ <button type="button" class="--advanced" onclick="resetSettings()">Reset settings</button>
69
+ <button type="button" class="--high-quality" onclick="toggleAdvancedSettings()">Toggle advanced settings</button>
70
+ </div>
71
+
72
+ </div>
73
+
74
+ <div class="participants-controls">
75
+ <div class="heading">Participants controls</div>
76
+ <div class="form-group">
77
+ <button type="button" onclick="createParticipants()">Create participants (without connect)</button>
78
+ <button type="button" onclick="destroyParticipants()">Destroy participants</button>
79
+ <br class="--advanced">
80
+ <button type="button" class="--advanced" onclick="createSingleParticipant()">Create single participant (without connect)</button>
81
+ <br>
82
+ <button type="button" onclick="joinParticipantsToRoom()">Join all participants to room</button>
83
+ <button type="button" onclick="disconnectParticipantsFromRoom()">Disconnect all participants from room</button>
84
+ <br class="--advanced">
85
+ <div class="form-group --advanced">
86
+ <label for="participants-names">Participants names (one name per line)</label>
87
+ <textarea id="participants-names" rows="5" onchange="applySettingsFromForm()"></textarea>
88
+ </div>
89
+ <button type="button" class="--advanced" onclick="setParticipantsNames()">Set Participants Names</button>
90
+ </div>
91
+
92
+ <div class="heading --advanced">Room controls</div>
93
+ <div class="form-group --advanced">
94
+ <button type="button" onclick="initiateFakeChat()">Initiate fake chat</button>
95
+ </div>
96
+ <div class="form-group --advanced">
97
+ <label for="emoji-sparking-interval">Emoji sparking interval (seconds)</label>
98
+ <input type="number" id="emoji-sparking-interval" min="5" step="1" onchange="applySettingsFromForm()">
99
+
100
+ <button type="button" onclick="enableEmojiSparking()">Enable Emoji Sparking</button>
101
+ <button type="button" onclick="disableEmojiSparking()">Disable Emoji Sparking</button>
102
+ <button type="button" onclick="burstEmojiSparking()">Burst Emoji Sparking</button>
103
+ </div>
104
+ </div>
105
+
106
+ <div class="participants-list-container">
107
+ <div class="heading">Participants</div>
108
+ <div id="participants-list"></div>
109
+ </div>
104
110
  </div>
105
111
 
106
112
  <script>
107
- // https://github.com/w3c/webcodecs/blob/f73dd2ccea32c0652ef94d48b87d728e4f709569/samples/image-decoder/animated-gif-renderer.html
108
-
109
- // ====================== Configuration ===================================
110
-
111
- const settingsUrlParameterName = 'settings';
112
-
113
- const defaultSettings = {
114
- advancedSettings: true,
115
- participantsCount: 5,
116
- participantsHighQuality: false,
117
- participantsOrderRandomization: false,
118
- minParticipantsCount: 1,
119
- maxParticipantsCount: 41,
120
- roomId: "",
121
- pinHash: "",
122
- connectionDelay: 1,
123
- parallelQueues: 1, // number of parallel queues for joining participants - only applied when connectionDelay is 0
124
- instanceType: "reactooDemo",
125
- swaggerUrl: "https://api.reactoo.com/v3/swagger.json",
126
- fetchNames: false,
127
- participantsNames: [],
128
- };
129
-
130
- const gifsCount_lq = 32; // Number of gifs in ./persons_gifs_lq folder
131
- const gifsCount_hq = 17; // Number of gifs in ./persons_gifs_hq folder
132
- let gifsCount = gifsCount_hq + gifsCount_lq;
133
-
134
- // ====================== General Variables ===============================
135
-
136
- let settings;
137
-
138
- const elements = {
139
- form: {
140
- participantsCount: document.getElementById("participants-count"),
141
- participantsHighQuality: document.getElementById("participants-high-quality"),
142
- participantsOrderRandomization: document.getElementById("participants-order-randomization"),
143
- roomId: document.getElementById("room-id"),
144
- pinHash: document.getElementById("pin-hash"),
145
- connectionDelay: document.getElementById("connection-delay"),
146
- parallelQueues: document.getElementById("parallel-queues"),
147
- instanceType: document.getElementById("instance-type"),
148
- swaggerUrl: document.getElementById("swagger-url"),
149
- fetchNames: document.getElementById("fetch-names"),
150
- participantsNames: document.getElementById("participants-names"),
151
- },
152
- participantsList: document.getElementById("participants-list"),
153
- };
154
-
155
- const participants = new Array(gifsCount).fill(undefined);
156
-
157
- let usedGifsIds = [];
158
-
159
- let usedGifsTimes = 0;
160
-
161
- const audioContext = new (window.AudioContext || window.webkitAudioContext)();
162
-
163
- // ====================== Helper Functions ================================
164
-
165
- function getUrlParameter(varName){
166
- const queryStr = decodeURI(window.location.search) + '&';
167
- const regex = new RegExp('.*?[&\\?]' + varName + '=(.*?)&.*');
168
- const val = queryStr.replace(regex, "$1");
169
- return val === queryStr ? false : val;
170
- }
171
-
172
- // ====================== Settings Functions ==============================
173
-
174
- function applySettingsFromForm() {
175
- settings = {
176
- advancedSettings: !!settings.advancedSettings,
177
- participantsCount: parseInt(elements.form.participantsCount.value) || defaultSettings.participantsCount,
178
- participantsHighQuality: elements.form.participantsHighQuality.checked,
179
- participantsOrderRandomization: elements.form.participantsOrderRandomization.checked,
180
- minParticipantsCount: parseInt(settings.minParticipantsCount) || defaultSettings.minParticipantsCount,
181
- maxParticipantsCount: parseInt(settings.maxParticipantsCount) || defaultSettings.maxParticipantsCount,
182
- roomId: elements.form.roomId.value || defaultSettings.roomId,
183
- pinHash: elements.form.pinHash.value || defaultSettings.pinHash,
184
- connectionDelay: parseFloat(elements.form.connectionDelay.value) || defaultSettings.connectionDelay,
185
- parallelQueues: parseInt(elements.form.parallelQueues.value) || defaultSettings.parallelQueues,
186
- instanceType: elements.form.instanceType.value || defaultSettings.instanceType,
187
- swaggerUrl: elements.form.swaggerUrl.value || defaultSettings.swaggerUrl,
188
- fetchNames: elements.form.fetchNames.checked,
189
- participantsNames: elements.form.participantsNames.value.split("\n") || "",
190
- };
191
-
192
- setSettingsToUrl(false);
193
- fillSettingsForm();
194
- }
195
-
196
- function setSettingsToUrl(deleteInstead = false) {
197
- const url = new URL(location.href);
198
- if (deleteInstead) {
199
- url.searchParams.delete(settingsUrlParameterName);
200
- } else {
201
- url.searchParams.set(settingsUrlParameterName, JSON.stringify(settings));
202
- }
203
- history.replaceState (null, '', url);
204
- }
205
-
206
- function getSettingsFromUrl() {
207
- let settingsFromUrl = getUrlParameter(settingsUrlParameterName);
208
-
209
- if (settingsFromUrl && typeof settingsFromUrl === 'string') {
210
- settingsFromUrl = JSON.parse(decodeURIComponent(settingsFromUrl.replace(/\+/g, ' ')));
211
-
212
- if (settingsFromUrl && typeof settingsFromUrl === 'object' && !Array.isArray(settingsFromUrl)) {
213
- settings = {...settings, ...settingsFromUrl};
214
- }
215
- }
216
- }
217
-
218
- function initializeSettings() {
219
- settings = {...defaultSettings};
220
- }
221
-
222
- function resetSettings() {
223
- initializeSettings();
224
- setSettingsToUrl(true);
225
- fillSettingsForm();
226
- }
227
-
228
- function toggleAdvancedSettings() {
229
- const advancedSettings = settings.advancedSettings;
230
- const participantsCount = settings.participantsCount;
231
- const roomId = settings.roomId;
232
-
233
- resetSettings();
234
-
235
- settings.advancedSettings = !advancedSettings;
236
- settings.participantsCount = participantsCount;
237
- settings.roomId = roomId;
238
-
239
- setSettingsToUrl(false);
240
- location.reload();
241
- }
242
-
243
- function fillSettingsForm() {
244
- elements.form.participantsCount.value = settings.participantsCount;
245
- elements.form.participantsHighQuality.checked = settings.participantsHighQuality;
246
- elements.form.participantsOrderRandomization.checked = settings.participantsOrderRandomization;
247
- elements.form.roomId.value = settings.roomId;
248
- elements.form.pinHash.value = settings.pinHash;
249
- elements.form.connectionDelay.value = settings.connectionDelay;
250
- elements.form.parallelQueues.value = settings.parallelQueues;
251
- elements.form.instanceType.value = settings.instanceType;
252
- elements.form.swaggerUrl.value = settings.swaggerUrl;
253
- elements.form.fetchNames.checked = settings.fetchNames;
254
- elements.form.participantsNames.value = settings.participantsNames.join("\n");
255
- }
256
-
257
- // ====================== Main Logic ==================================
258
-
259
- function getRandomNotUsedGifId() {
260
-
261
- if (settings.participantsHighQuality){
262
- gifsCount = gifsCount_hq;
263
- } else {
264
- gifsCount = gifsCount_lq;
265
- }
266
-
267
- if (usedGifsIds.length >= gifsCount) {
268
- usedGifsIds = [];
269
- usedGifsTimes++;
270
-
271
- }
272
-
273
- const availableGifsIds = new Array(gifsCount).fill().map((_, index) => index+1).filter(e => !usedGifsIds.includes(e));
274
- let randomNotUsedGifId;
275
-
276
-
277
- if (settings.participantsOrderRandomization) {
278
- randomNotUsedGifId = availableGifsIds[Math.floor(Math.random() * availableGifsIds.length)];
279
- } else {
280
- randomNotUsedGifId = Math.min(...availableGifsIds);
281
- }
282
-
283
- // console.log(randomNotUsedGifId);
284
-
285
- if (randomNotUsedGifId) {
286
- usedGifsIds.push(randomNotUsedGifId);
287
- return randomNotUsedGifId;
288
- } else {
289
- return null;
290
- }
291
- }
292
-
293
- function createParticipant(participantOrder) {
294
-
295
- // const gifId = getRandomNotUsedGifId();
296
- const gifId = getRandomNotUsedGifId() + usedGifsTimes * gifsCount;
297
-
298
- if (gifId === null) {
299
- return Promise.reject();
300
- }
301
-
302
- let gifUrl;
303
-
304
- if (settings.participantsHighQuality){
305
- // console.log("hq");
306
- gifUrl = `./persons_gifs_hq/${gifId - usedGifsTimes * gifsCount}.mp4`;
307
- } else {
308
- // console.log("lq");
309
- gifUrl = `./persons_gifs_lq/${gifId - usedGifsTimes * gifsCount}.gif`;
310
- }
311
-
312
- const frameIndex = 0;
313
- const sdkInstance = WatchTogetherSDK({debug: true, storagePrefix: `participant_${gifId}`, apiUrl: settings.swaggerUrl})({instanceType: settings.instanceType});
314
-
315
- const containerElement = document.createElement('div');
316
- containerElement.classList.add('participant-container');
317
- containerElement.setAttribute('data-participant-id', gifId);
318
- containerElement.style.order = participantOrder;
319
-
320
- let canvasElement;
321
- let canvasContext;
322
-
323
- if (settings.participantsHighQuality){
324
- canvasElement = document.createElement('video')
325
- canvasElement.setAttribute('title', `Video ID - ` + gifId);
326
- canvasElement.src = gifUrl;
327
- canvasElement.muted = false;
328
- canvasElement.height = 270;
329
- canvasElement.width = 480;
330
- canvasElement.autoplay = true;
331
- canvasElement.loop = true;
332
- containerElement.appendChild(canvasElement);
333
- // console.log(canvasElement);
334
- } else {
335
- canvasElement = document.createElement('canvas');
336
- canvasElement.setAttribute('title', `Gif ID - ` + gifId);
337
- canvasContext = canvasElement.getContext('2d');
338
- containerElement.appendChild(canvasElement);
339
- }
340
-
341
- // const canvasElement = document.createElement('canvas');
342
- // canvasElement.setAttribute('title', `Gif ID - ` + gifId);
343
- // const canvasContext = canvasElement.getContext('2d');
344
- // containerElement.appendChild(canvasElement);
345
-
346
- let spaceElement = document.createElement('div');
347
- spaceElement.classList.add('space');
348
- containerElement.appendChild(spaceElement);
349
-
350
- let participantNameElement;
351
- participantNameElement = document.createElement('input');
352
- participantNameElement.setAttribute('type', 'text');
353
- participantNameElement.setAttribute('placeholder', 'Name');
354
- participantNameElement.classList.add('--advanced');
355
- containerElement.appendChild(participantNameElement);
356
-
357
- const getNameButtonElement = document.createElement('button');
358
- getNameButtonElement.innerText = 'Get name';
359
- getNameButtonElement.addEventListener('click', () => {
360
- sdkInstance.user.getUserSelf()
361
- .then(response => {
362
- participants[gifId].displayname = response.data.displayname;
363
- participantNameElement.value = response.data.displayname;
364
- });
365
- });
366
- getNameButtonElement.classList.add('--advanced');
367
- containerElement.appendChild(getNameButtonElement);
368
-
369
- const setNameButtonElement = document.createElement('button');
370
- setNameButtonElement.innerText = 'Save name';
371
- setNameButtonElement.addEventListener('click', () => sdkInstance.user.updateUserSelf({lastRoomId: settings.roomId, displayname: participantNameElement.value}));
372
- setNameButtonElement.classList.add('--advanced');
373
- containerElement.appendChild(setNameButtonElement);
374
-
375
- spaceElement = document.createElement('div');
376
- spaceElement.classList.add('space', '--advanced');
377
- containerElement.appendChild(spaceElement);
378
-
379
- const destroyButtonElement = document.createElement('button');
380
- destroyButtonElement.innerText = 'Destroy';
381
- destroyButtonElement.addEventListener('click', () => destroyParticipant(participants[gifId]));
382
- destroyButtonElement.classList.add('--advanced');
383
- containerElement.appendChild(destroyButtonElement);
384
-
385
- const joinButtonElement = document.createElement('button');
386
- joinButtonElement.innerText = 'Join';
387
- joinButtonElement.addEventListener('click', () => joinParticipantToRoom(participants[gifId]));
388
- joinButtonElement.classList.add('--advanced');
389
- containerElement.appendChild(joinButtonElement);
390
-
391
- const leaveButtonElement = document.createElement('button');
392
- leaveButtonElement.innerText = 'Leave';
393
- leaveButtonElement.addEventListener('click', () => disconnectParticipantFromRoom(participants[gifId]));
394
- containerElement.appendChild(leaveButtonElement);
395
-
396
- spaceElement = document.createElement('div');
397
- spaceElement.classList.add('space', '--advanced');
398
- containerElement.appendChild(spaceElement);
399
-
400
- const toggleCameraButtonElement = document.createElement('button');
401
- toggleCameraButtonElement.innerText = 'Toggle camera';
402
- toggleCameraButtonElement.addEventListener('click', () => participants[gifId].session.toggleVideo());
403
- toggleCameraButtonElement.classList.add('--advanced');
404
- containerElement.appendChild(toggleCameraButtonElement);
405
-
406
- const toggleAudioButtonElement = document.createElement('button');
407
- toggleAudioButtonElement.innerText = 'Toggle audio';
408
- toggleAudioButtonElement.addEventListener('click', () => participants[gifId].session.toggleAudio());
409
- toggleAudioButtonElement.classList.add('--advanced');
410
- containerElement.appendChild(toggleAudioButtonElement);
411
-
412
- const toggleRaiseHandButtonElement = document.createElement('button');
413
- toggleRaiseHandButtonElement.innerText = 'Toggle raise hand';
414
- toggleRaiseHandButtonElement.addEventListener('click', () => {
415
- sdkInstance.room.setUser({roomId: settings.roomId, userId: participants[gifId].sdkInstance.userId, flag: participants[gifId].handRaised ? 'handLower' : 'handRaise'});
416
- participants[gifId].handRaised = !participants[gifId].handRaised;
417
- });
418
- toggleRaiseHandButtonElement.classList.add('--advanced');
419
- containerElement.appendChild(toggleRaiseHandButtonElement);
420
-
421
- let participant;
422
-
423
- if (settings.participantsHighQuality){
424
- participant = {
425
- gifId,
426
- gifUrl,
427
- frameIndex,
428
- reverseOrder: false, // boomerang animation
429
- width: canvasElement.width, // Will be set after first frame is decoded
430
- height: canvasElement.height, // Will be set after first frame is decoded
431
- canvas: canvasElement,
432
- sdkInstance,
433
- loginPromise: sdkInstance.auth.deviceLogin(false, gifId),
434
- session: null, // Will be set after room join
435
- handRaised: false,
436
- };
437
-
438
- if (settings.fetchNames) {
439
- participant.loginPromise.then(() => sdkInstance.user.getUserSelf())
440
- .then(response => {
441
- participant.displayname = response.data.displayname;
442
- participantNameElement.value = response.data.displayname;
443
- });
444
-
445
- }
446
-
447
- elements.participantsList.appendChild(containerElement);
448
- return Promise.resolve(participant);
449
-
450
-
451
- } else {
452
- return fetch(gifUrl)
453
- .then(response => {
454
- const imageDecoder = new ImageDecoder({data: response.body, type: 'image/gif'});
455
-
456
- // if(typeof canvasElement !== "object") {
457
- // console.log(settings.participantsHighQuality, typeof settings.participantsHighQuality, typeof canvasElement);
458
- // }
459
-
460
- participant = {
461
- gifId,
462
- gifUrl,
463
- imageDecoder,
464
- frameIndex,
465
- reverseOrder: false, // boomerang animation
466
- width: null, // Will be set after first frame is decoded
467
- height: null, // Will be set after first frame is decoded
468
- canvas: canvasElement,
469
- canvasContext,
470
- sdkInstance,
471
- loginPromise: sdkInstance.auth.deviceLogin(false, gifId),
472
- session: null, // Will be set after room join
473
- handRaised: false,
474
- };
475
-
476
- if (settings.fetchNames) {
477
- participant.loginPromise.then(() => sdkInstance.user.getUserSelf())
478
- .then(response => {
479
- participant.displayname = response.data.displayname;
480
- participantNameElement.value = response.data.displayname;
481
- });
482
- }
483
-
484
- return imageDecoder
485
- .decode({frameIndex})
486
- .then(decodeResult => {
487
- participant.width = decodeResult.image.displayWidth;
488
- participant.canvas.width = decodeResult.image.displayWidth;
489
-
490
- participant.height = decodeResult.image.displayHeight;
491
- participant.canvas.height = decodeResult.image.displayHeight;
492
-
493
- participant.canvas.id = `canvas-${participant.gifId}`;
494
-
495
- // Start render loop
496
- renderGif(decodeResult, participant);
497
-
498
- elements.participantsList.appendChild(containerElement);
499
- return participant;
500
- });
501
- });
502
- }
503
-
504
- }
505
-
506
- function createParticipants(participantsCount = settings.participantsCount) {
507
-
508
- destroyParticipants()
509
- .then(() => {
510
- for (let i = 0; i < participantsCount; i++) {
511
- createParticipant(i).then(participant => {
512
- participants[participant.gifId] = participant;
513
- });
514
- }
515
- })
516
- }
517
-
518
- function initiazeFakeChat() {
519
- req = "https://api.reactoo.com/v3/wt/room?demo=true&id=" + elements.form.roomId.value;
520
-
521
- const sdkInstance = WatchTogetherSDK({debug: true, storagePrefix: `participant_fakeChat`, apiUrl: settings.swaggerUrl})({instanceType: settings.instanceType});
522
-
523
- sdkInstance.auth.deviceLogin(false, "participant_fakeChat").then(() => {
524
- sdkInstance.room.getRoomById(elements.form.roomId.value, elements.form.pinHash.value, undefined, true)
525
- .then(response => {
526
- console.log(response);
527
- })
528
- .catch(error => {
529
- console.log(error);
530
- });
531
- });
532
- }
533
-
534
- function createSingleParticipant() {
535
- const participantOrder = participants.filter(p => p).length;
536
-
537
- console.log(participantOrder);
538
-
539
-
540
- createParticipant(participantOrder)
541
- .then(participant => participants[participant.gifId] = participant);
542
- }
543
-
544
- function destroyParticipant(participant) {
545
- disconnectParticipantFromRoom(participant)
546
- .then(() => {
547
- if (participant.imageDecoder) {
548
- participant.imageDecoder.close();
549
- }
550
-
551
- participant.sdkInstance.auth.logout();
552
- usedGifsIds.splice(usedGifsIds.indexOf(participant.gifId), 1);
553
- document.querySelector('.participant-container[data-participant-id="' + participant.gifId + '"]').remove();
554
-
555
- participants[participant.gifId] = undefined;
556
- });
557
- }
558
-
559
- function destroyParticipants() {
560
- return disconnectParticipantsFromRoom()
561
- .then(() => {
562
- participants.filter(p => p).forEach(participant => {
563
- if (participant.imageDecoder) {
564
- participant.imageDecoder.close();
565
- }
566
- participant.sdkInstance.auth.logout();
567
- usedGifsIds.splice(usedGifsIds.indexOf(participant.gifId), 1);
568
- document.querySelector('.participant-container[data-participant-id="' + participant.gifId + '"]').remove();
569
- });
570
-
571
- for (let i = 0 ; i < participants.length ; i++) {
572
- if (participants[i]) {
573
- participants[i] = undefined;
574
- }
575
- }
576
- usedGifsTimes = 0;
577
- });
578
- }
579
-
580
- function joinParticipantToRoom(participant) {
581
- return participant.loginPromise.then(() => {
582
- participant.sdkInstance.room.createSession({roomId: settings.roomId, pinHash: settings.pinHash})
583
- .then(session => {
584
- participant.session = session;
585
- return Promise.all([session, session.connect()])
586
- })
587
- .then(([session, _]) => {
588
- return getAudioStream()
589
- .then(audioStream => {
590
- participant.stream = participant.canvas.captureStream();
591
- if (participant.stream.getAudioTracks().length === 0) {
592
- participant.stream.addTrack(audioStream.getAudioTracks()[0]);
593
- }
594
- return session.publishLocal(participant.stream, 'camera0')
595
- // .then(() => participant.session.toggleAudio(false));
596
- });
597
- });
598
- });
599
- }
600
-
601
- function joinParticipantsToRoom () {
602
- if (settings.connectionDelay === 0 && settings.parallelQueues > 0) {
603
- // Parallel join
604
-
605
- const parallelQueues = new Array(participants.filter(p => p).length).fill().map(() => Promise.resolve());
606
-
607
- for (let i = 0 ; i < participants.filter(p => p).length ; i++) {
608
- const parallelQueueIndex = i % settings.parallelQueues;
609
- parallelQueues[parallelQueueIndex] = parallelQueues[parallelQueueIndex]
610
- .then(() => joinParticipantToRoom(participants.filter(p => p)[i]))
611
- .then(() => new Promise(resolve => setTimeout(resolve, settings.connectionDelay * 1000)));
612
- }
613
- } else {
614
- // Serial join
615
-
616
- return participants.filter(p => p).reduce((promiseChain, participant) => {
617
- return promiseChain.then(() => joinParticipantToRoom(participant))
618
- .then(() => new Promise(resolve => setTimeout(resolve, settings.connectionDelay * 1000)));
619
- }, Promise.resolve());
620
- }
621
- }
622
-
623
- function disconnectParticipantFromRoom(participant) {
624
- if (participant && participant.loginPromise) {
625
- return participant.loginPromise
626
- .then(() => {
627
- if (participant.session && participant.session.disconnect) {
628
- return participant.session.disconnect();
629
- }
630
- })
631
- .then(() => participant.session = null);
632
- }
633
- }
634
-
635
- function disconnectParticipantsFromRoom() {
636
- return Promise.all(participants.filter(p => p).map(participant => disconnectParticipantFromRoom(participant)));
637
- }
638
-
639
- function renderGif(decodeResult, participant) {
640
- participant.canvasContext.drawImage(decodeResult.image, 0, 0);
641
-
642
- const track = participant.imageDecoder.tracks.selectedTrack;
643
-
644
- if (!track) {
645
- return;
646
- }
647
-
648
- // We check complete here since `frameCount` won't be stable until all data
649
- // has been received. This may cause us to receive a RangeError during the
650
- // decode() call below which needs to be handled.
651
- if (participant.imageDecoder.complete) {
652
- if (track.frameCount == 1) {
653
- return;
654
- }
655
-
656
- if ((participant.reverseOrder && participant.frameIndex - 1 <= 0) || (!participant.reverseOrder && participant.frameIndex + 1 >= track.frameCount)) {
657
- participant.reverseOrder = !participant.reverseOrder;
658
- }
659
- }
660
-
661
- if (participant.reverseOrder) {
662
- --participant.frameIndex;
663
- } else {
664
- ++participant.frameIndex;
665
- }
666
-
667
- // Decode the next frame ahead of display so it's ready in time.
668
- participant.imageDecoder.decode({frameIndex: participant.frameIndex})
669
- .then( nextDecodeResult => setTimeout(_ => {
670
- renderGif(nextDecodeResult, participant);
671
- }, decodeResult.image.duration / 1000.0))
672
- .catch(e => {
673
- // We can end up requesting an imageIndex past the end since we're using
674
- // a ReadableStrem from fetch(), when this happens just wrap around.
675
- if (e instanceof RangeError) {
676
- participant.frameIndex = 0;
677
- participant.imageDecoder.decode({frameIndex: imageIndex}).then(decodeResult => renderGif(decodeResult, participant));
678
- } else {
679
- throw e;
680
- }
681
- });
682
- }
683
-
684
- function setParticipantsNames() {
685
- participants.filter(p => p).forEach((participant, participantOrder) => {
686
- if (settings.participantsNames[participantOrder]) {
687
- participant.sdkInstance.user.updateUserSelf({lastRoomId: settings.roomId, displayname: settings.participantsNames[participantOrder]});
688
- } else {
689
- console.log('Not enough names');
690
- }
691
- });
692
- }
693
-
694
- function getAudioStream() {
695
- return new Promise(resolve => {
696
- const request = new XMLHttpRequest();
697
- // https://ttsmp3.com/
698
- request.open('GET', './sound_empty.mp3', true);
699
- request.responseType = 'arraybuffer';
700
- request.onload = function() {
701
- audioContext.decodeAudioData(request.response)
702
- .then(buffer => {
703
- const streamDestination = audioContext.createMediaStreamDestination();
704
- const source = audioContext.createBufferSource();
705
- source.buffer = buffer;
706
- source.connect(streamDestination);
707
- source.loop = true;
708
- source.start();
709
-
710
- new Audio().srcObject = streamDestination.stream;
711
-
712
- resolve(streamDestination.stream);
713
- });
714
- }
715
- request.send();
716
- });
717
- }
718
-
719
- // ====================== Initialization ==============================
720
-
721
- document.addEventListener("DOMContentLoaded", function() {
722
- initializeSettings();
723
- getSettingsFromUrl();
724
-
725
- if (!settings.advancedSettings) {
726
- settings.participantsHighQuality = true;
727
- document.head.insertAdjacentHTML("beforeend", `<style>.--advanced{display:none}</style>`)
728
- }
729
-
730
- fillSettingsForm();
731
- });
113
+ // https://github.com/w3c/webcodecs/blob/f73dd2ccea32c0652ef94d48b87d728e4f709569/samples/image-decoder/animated-gif-renderer.html
114
+
115
+ // ====================== Configuration ===================================
116
+
117
+ const settingsUrlParameterName = 'settings';
118
+
119
+ const defaultSettings = {
120
+ advancedSettings: false,
121
+ participantsCount: 5,
122
+ participantsHighQuality: false,
123
+ participantsOrderRandomization: false,
124
+ minParticipantsCount: 1,
125
+ maxParticipantsCount: 41,
126
+ roomId: "",
127
+ pinHash: "",
128
+ connectionDelay: 1,
129
+ parallelQueues: 1, // number of parallel queues for joining participants - only applied when connectionDelay is 0
130
+ instanceType: "reactooDemo",
131
+ swaggerUrl: "https://api.reactoo.com/v3/swagger.json",
132
+ fetchNames: false,
133
+ participantsNames: [],
134
+ emojiSparkingInterval: 180,
135
+ };
136
+
137
+ const gifsCount_lq = 32; // Number of gifs in ./persons_gifs_lq folder
138
+ const gifsCount_hq = 17; // Number of gifs in ./persons_gifs_hq folder
139
+ let gifsCount = gifsCount_hq + gifsCount_lq;
140
+ const sparkingEmojis = ['laugh', 'love', 'wow', 'clap', 'thumbsup', 'thumbsdown', 'fire'];
141
+
142
+ // ====================== General Variables ===============================
143
+
144
+ let settings;
145
+
146
+ const elements = {
147
+ form: {
148
+ participantsCount: document.getElementById("participants-count"),
149
+ participantsHighQuality: document.getElementById("participants-high-quality"),
150
+ participantsOrderRandomization: document.getElementById("participants-order-randomization"),
151
+ roomId: document.getElementById("room-id"),
152
+ pinHash: document.getElementById("pin-hash"),
153
+ connectionDelay: document.getElementById("connection-delay"),
154
+ parallelQueues: document.getElementById("parallel-queues"),
155
+ instanceType: document.getElementById("instance-type"),
156
+ swaggerUrl: document.getElementById("swagger-url"),
157
+ fetchNames: document.getElementById("fetch-names"),
158
+ participantsNames: document.getElementById("participants-names"),
159
+ emojiSparkingInterval: document.getElementById("emoji-sparking-interval"),
160
+ },
161
+ participantsList: document.getElementById("participants-list"),
162
+ };
163
+
164
+ const participants = new Array(gifsCount).fill(undefined);
165
+
166
+ let usedGifsIds = [];
167
+
168
+ let usedGifsTimes = 0;
169
+
170
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
171
+
172
+ // ====================== Helper Functions ================================
173
+
174
+ function getUrlParameter(varName){
175
+ const queryStr = decodeURI(window.location.search) + '&';
176
+ const regex = new RegExp('.*?[&\\?]' + varName + '=(.*?)&.*');
177
+ const val = queryStr.replace(regex, "$1");
178
+ return val === queryStr ? false : val;
179
+ }
180
+
181
+ // ====================== Settings Functions ==============================
182
+
183
+ function applySettingsFromForm() {
184
+ settings = {
185
+ advancedSettings: !!settings.advancedSettings,
186
+ participantsCount: parseInt(elements.form.participantsCount.value) || defaultSettings.participantsCount,
187
+ participantsHighQuality: elements.form.participantsHighQuality.checked,
188
+ participantsOrderRandomization: elements.form.participantsOrderRandomization.checked,
189
+ minParticipantsCount: parseInt(settings.minParticipantsCount) || defaultSettings.minParticipantsCount,
190
+ maxParticipantsCount: parseInt(settings.maxParticipantsCount) || defaultSettings.maxParticipantsCount,
191
+ roomId: elements.form.roomId.value || defaultSettings.roomId,
192
+ pinHash: elements.form.pinHash.value || defaultSettings.pinHash,
193
+ connectionDelay: parseFloat(elements.form.connectionDelay.value) || defaultSettings.connectionDelay,
194
+ parallelQueues: parseInt(elements.form.parallelQueues.value) || defaultSettings.parallelQueues,
195
+ instanceType: elements.form.instanceType.value || defaultSettings.instanceType,
196
+ swaggerUrl: elements.form.swaggerUrl.value || defaultSettings.swaggerUrl,
197
+ fetchNames: elements.form.fetchNames.checked,
198
+ participantsNames: elements.form.participantsNames.value.split("\n") || "",
199
+ emojiSparkingInterval: parseFloat(elements.form.emojiSparkingInterval.value) || defaultSettings.emojiSparkingInterval,
200
+ };
201
+
202
+ setSettingsToUrl(false);
203
+ fillSettingsForm();
204
+ }
205
+
206
+ function setSettingsToUrl(deleteInstead = false) {
207
+ const url = new URL(location.href);
208
+ if (deleteInstead) {
209
+ url.searchParams.delete(settingsUrlParameterName);
210
+ } else {
211
+ url.searchParams.set(settingsUrlParameterName, JSON.stringify(settings));
212
+ }
213
+ history.replaceState (null, '', url);
214
+ }
215
+
216
+ function getSettingsFromUrl() {
217
+ let settingsFromUrl = getUrlParameter(settingsUrlParameterName);
218
+
219
+ if (settingsFromUrl && typeof settingsFromUrl === 'string') {
220
+ settingsFromUrl = JSON.parse(decodeURIComponent(settingsFromUrl.replace(/\+/g, ' ')));
221
+
222
+ if (settingsFromUrl && typeof settingsFromUrl === 'object' && !Array.isArray(settingsFromUrl)) {
223
+ settings = {...settings, ...settingsFromUrl};
224
+ }
225
+ }
226
+ }
227
+
228
+ function initializeSettings() {
229
+ settings = {...defaultSettings};
230
+ }
231
+
232
+ function resetSettings() {
233
+ initializeSettings();
234
+ setSettingsToUrl(true);
235
+ fillSettingsForm();
236
+ }
237
+
238
+ function toggleAdvancedSettings() {
239
+ const advancedSettings = settings.advancedSettings;
240
+ const participantsCount = settings.participantsCount;
241
+ const roomId = settings.roomId;
242
+
243
+ resetSettings();
244
+
245
+ settings.advancedSettings = !advancedSettings;
246
+ settings.participantsCount = participantsCount;
247
+ settings.roomId = roomId;
248
+
249
+ setSettingsToUrl(false);
250
+ location.reload();
251
+ }
252
+
253
+ function fillSettingsForm() {
254
+ elements.form.participantsCount.value = settings.participantsCount;
255
+ elements.form.participantsHighQuality.checked = settings.participantsHighQuality;
256
+ elements.form.participantsOrderRandomization.checked = settings.participantsOrderRandomization;
257
+ elements.form.roomId.value = settings.roomId;
258
+ elements.form.pinHash.value = settings.pinHash;
259
+ elements.form.connectionDelay.value = settings.connectionDelay;
260
+ elements.form.parallelQueues.value = settings.parallelQueues;
261
+ elements.form.instanceType.value = settings.instanceType;
262
+ elements.form.swaggerUrl.value = settings.swaggerUrl;
263
+ elements.form.fetchNames.checked = settings.fetchNames;
264
+ elements.form.participantsNames.value = settings.participantsNames.join("\n");
265
+ elements.form.emojiSparkingInterval.value = settings.emojiSparkingInterval;
266
+ }
267
+
268
+ // ====================== Main Logic ==================================
269
+
270
+ function getRandomNotUsedGifId() {
271
+
272
+ if (settings.participantsHighQuality){
273
+ gifsCount = gifsCount_hq;
274
+ } else {
275
+ gifsCount = gifsCount_lq;
276
+ }
277
+
278
+ if (usedGifsIds.length >= gifsCount) {
279
+ usedGifsIds = [];
280
+ usedGifsTimes++;
281
+
282
+ }
283
+
284
+ const availableGifsIds = new Array(gifsCount).fill().map((_, index) => index+1).filter(e => !usedGifsIds.includes(e));
285
+ let randomNotUsedGifId;
286
+
287
+
288
+ if (settings.participantsOrderRandomization) {
289
+ randomNotUsedGifId = availableGifsIds[Math.floor(Math.random() * availableGifsIds.length)];
290
+ } else {
291
+ randomNotUsedGifId = Math.min(...availableGifsIds);
292
+ }
293
+
294
+ // console.log(randomNotUsedGifId);
295
+
296
+ if (randomNotUsedGifId) {
297
+ usedGifsIds.push(randomNotUsedGifId);
298
+ return randomNotUsedGifId;
299
+ } else {
300
+ return null;
301
+ }
302
+ }
303
+
304
+ function createParticipant(participantOrder) {
305
+
306
+ // const gifId = getRandomNotUsedGifId();
307
+ const gifId = getRandomNotUsedGifId() + usedGifsTimes * gifsCount;
308
+
309
+ if (gifId === null) {
310
+ return Promise.reject();
311
+ }
312
+
313
+ let gifUrl;
314
+
315
+ if (settings.participantsHighQuality){
316
+ // console.log("hq");
317
+ gifUrl = `./persons_gifs_hq/${gifId - usedGifsTimes * gifsCount}.mp4`;
318
+ } else {
319
+ // console.log("lq");
320
+ gifUrl = `./persons_gifs_lq/${gifId - usedGifsTimes * gifsCount}.gif`;
321
+ }
322
+
323
+ const frameIndex = 0;
324
+ const sdkInstance = WatchTogetherSDK({debug: true, storagePrefix: `participant_${gifId}`, apiUrl: settings.swaggerUrl})({instanceType: settings.instanceType});
325
+
326
+ const containerElement = document.createElement('div');
327
+ containerElement.classList.add('participant-container');
328
+ containerElement.setAttribute('data-participant-id', gifId);
329
+ containerElement.style.order = participantOrder;
330
+
331
+ let canvasElement;
332
+ let canvasContext;
333
+
334
+ if (settings.participantsHighQuality){
335
+ canvasElement = document.createElement('video')
336
+ canvasElement.setAttribute('title', `Video ID - ` + gifId);
337
+ canvasElement.src = gifUrl;
338
+ canvasElement.muted = false;
339
+ canvasElement.height = 270;
340
+ canvasElement.width = 480;
341
+ canvasElement.autoplay = true;
342
+ canvasElement.loop = true;
343
+ containerElement.appendChild(canvasElement);
344
+ // console.log(canvasElement);
345
+ } else {
346
+ canvasElement = document.createElement('canvas');
347
+ canvasElement.setAttribute('title', `Gif ID - ` + gifId);
348
+ canvasContext = canvasElement.getContext('2d');
349
+ containerElement.appendChild(canvasElement);
350
+ }
351
+
352
+ // const canvasElement = document.createElement('canvas');
353
+ // canvasElement.setAttribute('title', `Gif ID - ` + gifId);
354
+ // const canvasContext = canvasElement.getContext('2d');
355
+ // containerElement.appendChild(canvasElement);
356
+
357
+ let spaceElement = document.createElement('div');
358
+ spaceElement.classList.add('space');
359
+ containerElement.appendChild(spaceElement);
360
+
361
+ let participantNameElement;
362
+ participantNameElement = document.createElement('input');
363
+ participantNameElement.setAttribute('type', 'text');
364
+ participantNameElement.setAttribute('placeholder', 'Name');
365
+ participantNameElement.classList.add('--advanced');
366
+ containerElement.appendChild(participantNameElement);
367
+
368
+ const getNameButtonElement = document.createElement('button');
369
+ getNameButtonElement.innerText = 'Get name';
370
+ getNameButtonElement.addEventListener('click', () => {
371
+ sdkInstance.user.getUserSelf()
372
+ .then(response => {
373
+ participants[gifId].displayname = response.data.displayname;
374
+ participantNameElement.value = response.data.displayname;
375
+ });
376
+ });
377
+ getNameButtonElement.classList.add('--advanced');
378
+ containerElement.appendChild(getNameButtonElement);
379
+
380
+ const setNameButtonElement = document.createElement('button');
381
+ setNameButtonElement.innerText = 'Save name';
382
+ setNameButtonElement.addEventListener('click', () => sdkInstance.user.updateUserSelf({lastRoomId: settings.roomId, displayname: participantNameElement.value}));
383
+ setNameButtonElement.classList.add('--advanced');
384
+ containerElement.appendChild(setNameButtonElement);
385
+
386
+ spaceElement = document.createElement('div');
387
+ spaceElement.classList.add('space', '--advanced');
388
+ containerElement.appendChild(spaceElement);
389
+
390
+ const destroyButtonElement = document.createElement('button');
391
+ destroyButtonElement.innerText = 'Destroy';
392
+ destroyButtonElement.addEventListener('click', () => destroyParticipant(participants[gifId]));
393
+ destroyButtonElement.classList.add('--advanced');
394
+ containerElement.appendChild(destroyButtonElement);
395
+
396
+ const joinButtonElement = document.createElement('button');
397
+ joinButtonElement.innerText = 'Join';
398
+ joinButtonElement.addEventListener('click', () => joinParticipantToRoom(participants[gifId]));
399
+ joinButtonElement.classList.add('--advanced');
400
+ containerElement.appendChild(joinButtonElement);
401
+
402
+ const leaveButtonElement = document.createElement('button');
403
+ leaveButtonElement.innerText = 'Leave';
404
+ leaveButtonElement.addEventListener('click', () => disconnectParticipantFromRoom(participants[gifId]));
405
+ containerElement.appendChild(leaveButtonElement);
406
+
407
+ spaceElement = document.createElement('div');
408
+ spaceElement.classList.add('space', '--advanced');
409
+ containerElement.appendChild(spaceElement);
410
+
411
+ const toggleCameraButtonElement = document.createElement('button');
412
+ toggleCameraButtonElement.innerText = 'Toggle camera';
413
+ toggleCameraButtonElement.addEventListener('click', () => participants[gifId].session.toggleVideo());
414
+ toggleCameraButtonElement.classList.add('--advanced');
415
+ containerElement.appendChild(toggleCameraButtonElement);
416
+
417
+ const toggleAudioButtonElement = document.createElement('button');
418
+ toggleAudioButtonElement.innerText = 'Toggle audio';
419
+ toggleAudioButtonElement.addEventListener('click', () => participants[gifId].session.toggleAudio());
420
+ toggleAudioButtonElement.classList.add('--advanced');
421
+ containerElement.appendChild(toggleAudioButtonElement);
422
+
423
+ const toggleRaiseHandButtonElement = document.createElement('button');
424
+ toggleRaiseHandButtonElement.innerText = 'Toggle raise hand';
425
+ toggleRaiseHandButtonElement.addEventListener('click', () => {
426
+ sdkInstance.room.setUser({roomId: settings.roomId, userId: participants[gifId].sdkInstance.userId, flag: participants[gifId].handRaised ? 'handLower' : 'handRaise'});
427
+ participants[gifId].handRaised = !participants[gifId].handRaised;
428
+ });
429
+ toggleRaiseHandButtonElement.classList.add('--advanced');
430
+ containerElement.appendChild(toggleRaiseHandButtonElement);
431
+
432
+ const sparkEmojiButtonElement = document.createElement('button');
433
+ sparkEmojiButtonElement.innerText = 'Spark Emoji';
434
+ sparkEmojiButtonElement.addEventListener('click', () => {
435
+ sparkEmoji(participants[gifId]);
436
+ });
437
+ sparkEmojiButtonElement.classList.add('--advanced');
438
+ containerElement.appendChild(sparkEmojiButtonElement);
439
+
440
+ let participant;
441
+
442
+ if (settings.participantsHighQuality){
443
+ participant = {
444
+ gifId,
445
+ gifUrl,
446
+ frameIndex,
447
+ reverseOrder: false, // boomerang animation
448
+ width: canvasElement.width, // Will be set after first frame is decoded
449
+ height: canvasElement.height, // Will be set after first frame is decoded
450
+ canvas: canvasElement,
451
+ sdkInstance,
452
+ loginPromise: sdkInstance.auth.deviceLogin(false, gifId),
453
+ session: null, // Will be set after room join
454
+ handRaised: false,
455
+ };
456
+
457
+ if (settings.fetchNames) {
458
+ participant.loginPromise.then(() => sdkInstance.user.getUserSelf())
459
+ .then(response => {
460
+ participant.displayname = response.data.displayname;
461
+ participantNameElement.value = response.data.displayname;
462
+ });
463
+
464
+ }
465
+
466
+ elements.participantsList.appendChild(containerElement);
467
+ return Promise.resolve(participant);
468
+
469
+
470
+ } else {
471
+ return fetch(gifUrl)
472
+ .then(response => {
473
+ const imageDecoder = new ImageDecoder({data: response.body, type: 'image/gif'});
474
+
475
+ // if(typeof canvasElement !== "object") {
476
+ // console.log(settings.participantsHighQuality, typeof settings.participantsHighQuality, typeof canvasElement);
477
+ // }
478
+
479
+ participant = {
480
+ gifId,
481
+ gifUrl,
482
+ imageDecoder,
483
+ frameIndex,
484
+ reverseOrder: false, // boomerang animation
485
+ width: null, // Will be set after first frame is decoded
486
+ height: null, // Will be set after first frame is decoded
487
+ canvas: canvasElement,
488
+ canvasContext,
489
+ sdkInstance,
490
+ loginPromise: sdkInstance.auth.deviceLogin(false, gifId),
491
+ session: null, // Will be set after room join
492
+ handRaised: false,
493
+ emojiSparkingTimeoutId: null,
494
+ };
495
+
496
+ if (settings.fetchNames) {
497
+ participant.loginPromise.then(() => sdkInstance.user.getUserSelf())
498
+ .then(response => {
499
+ participant.displayname = response.data.displayname;
500
+ participantNameElement.value = response.data.displayname;
501
+ });
502
+ }
503
+
504
+ return imageDecoder
505
+ .decode({frameIndex})
506
+ .then(decodeResult => {
507
+ participant.width = decodeResult.image.displayWidth;
508
+ participant.canvas.width = decodeResult.image.displayWidth;
509
+
510
+ participant.height = decodeResult.image.displayHeight;
511
+ participant.canvas.height = decodeResult.image.displayHeight;
512
+
513
+ participant.canvas.id = `canvas-${participant.gifId}`;
514
+
515
+ // Start render loop
516
+ renderGif(decodeResult, participant);
517
+
518
+ elements.participantsList.appendChild(containerElement);
519
+ return participant;
520
+ });
521
+ });
522
+ }
523
+
524
+ }
525
+
526
+ function createParticipants(participantsCount = settings.participantsCount) {
527
+
528
+ destroyParticipants()
529
+ .then(() => {
530
+ for (let i = 0; i < participantsCount; i++) {
531
+ createParticipant(i).then(participant => {
532
+ participants[participant.gifId] = participant;
533
+ });
534
+ }
535
+ })
536
+ }
537
+
538
+ function initiateFakeChat() {
539
+ req = "https://api.reactoo.com/v3/wt/room?demo=true&id=" + elements.form.roomId.value;
540
+
541
+ const sdkInstance = WatchTogetherSDK({debug: true, storagePrefix: `participant_fakeChat`, apiUrl: settings.swaggerUrl})({instanceType: settings.instanceType});
542
+
543
+ sdkInstance.auth.deviceLogin(false, "participant_fakeChat").then(() => {
544
+ sdkInstance.room.getRoomById(elements.form.roomId.value, elements.form.pinHash.value, undefined, true)
545
+ .then(response => {
546
+ console.log(response);
547
+ })
548
+ .catch(error => {
549
+ console.log(error);
550
+ });
551
+ });
552
+ }
553
+
554
+ function createSingleParticipant() {
555
+ const participantOrder = participants.filter(p => p).length;
556
+
557
+ console.log(participantOrder);
558
+
559
+
560
+ createParticipant(participantOrder)
561
+ .then(participant => participants[participant.gifId] = participant);
562
+ }
563
+
564
+ function destroyParticipant(participant) {
565
+ disconnectParticipantFromRoom(participant)
566
+ .then(() => {
567
+ if (participant.imageDecoder) {
568
+ participant.imageDecoder.close();
569
+ }
570
+
571
+ participant.sdkInstance.auth.logout();
572
+ usedGifsIds.splice(usedGifsIds.indexOf(participant.gifId), 1);
573
+ document.querySelector('.participant-container[data-participant-id="' + participant.gifId + '"]').remove();
574
+
575
+ participants[participant.gifId] = undefined;
576
+ });
577
+ }
578
+
579
+ function destroyParticipants() {
580
+ return disconnectParticipantsFromRoom()
581
+ .then(() => {
582
+ participants.filter(p => p).forEach(participant => {
583
+ if (participant.imageDecoder) {
584
+ participant.imageDecoder.close();
585
+ }
586
+ participant.sdkInstance.auth.logout();
587
+ usedGifsIds.splice(usedGifsIds.indexOf(participant.gifId), 1);
588
+ document.querySelector('.participant-container[data-participant-id="' + participant.gifId + '"]').remove();
589
+ });
590
+
591
+ for (let i = 0 ; i < participants.length ; i++) {
592
+ if (participants[i]) {
593
+ participants[i] = undefined;
594
+ }
595
+ }
596
+ usedGifsTimes = 0;
597
+ });
598
+ }
599
+
600
+ function joinParticipantToRoom(participant) {
601
+ return participant.loginPromise.then(() => {
602
+ participant.sdkInstance.room.createSession({roomId: settings.roomId, pinHash: settings.pinHash})
603
+ .then(session => {
604
+ participant.session = session;
605
+ return Promise.all([session, session.connect()])
606
+ })
607
+ .then(([session, _]) => {
608
+ return getAudioStream()
609
+ .then(audioStream => {
610
+ participant.stream = participant.canvas.captureStream();
611
+ if (participant.stream.getAudioTracks().length === 0) {
612
+ participant.stream.addTrack(audioStream.getAudioTracks()[0]);
613
+ }
614
+ return session.publishLocal(participant.stream, 'camera0')
615
+ // .then(() => participant.session.toggleAudio(false));
616
+ });
617
+ });
618
+ });
619
+ }
620
+
621
+ function joinParticipantsToRoom () {
622
+ if (settings.connectionDelay === 0 && settings.parallelQueues > 0) {
623
+ // Parallel join
624
+
625
+ const parallelQueues = new Array(participants.filter(p => p).length).fill().map(() => Promise.resolve());
626
+
627
+ for (let i = 0 ; i < participants.filter(p => p).length ; i++) {
628
+ const parallelQueueIndex = i % settings.parallelQueues;
629
+ parallelQueues[parallelQueueIndex] = parallelQueues[parallelQueueIndex]
630
+ .then(() => joinParticipantToRoom(participants.filter(p => p)[i]))
631
+ .then(() => new Promise(resolve => setTimeout(resolve, settings.connectionDelay * 1000)));
632
+ }
633
+ } else {
634
+ // Serial join
635
+
636
+ return participants.filter(p => p).reduce((promiseChain, participant) => {
637
+ return promiseChain.then(() => joinParticipantToRoom(participant))
638
+ .then(() => new Promise(resolve => setTimeout(resolve, settings.connectionDelay * 1000)));
639
+ }, Promise.resolve());
640
+ }
641
+ }
642
+
643
+ function disconnectParticipantFromRoom(participant) {
644
+ if (participant && participant.loginPromise) {
645
+ return participant.loginPromise
646
+ .then(() => {
647
+ if (participant.session && participant.session.disconnect) {
648
+ return participant.session.disconnect();
649
+ }
650
+ })
651
+ .then(() => participant.session = null);
652
+ }
653
+ }
654
+
655
+ function disconnectParticipantsFromRoom() {
656
+ return Promise.all(participants.filter(p => p).map(participant => disconnectParticipantFromRoom(participant)));
657
+ }
658
+
659
+ function renderGif(decodeResult, participant) {
660
+ participant.canvasContext.drawImage(decodeResult.image, 0, 0);
661
+
662
+ const track = participant.imageDecoder.tracks.selectedTrack;
663
+
664
+ if (!track) {
665
+ return;
666
+ }
667
+
668
+ // We check complete here since `frameCount` won't be stable until all data
669
+ // has been received. This may cause us to receive a RangeError during the
670
+ // decode() call below which needs to be handled.
671
+ if (participant.imageDecoder.complete) {
672
+ if (track.frameCount == 1) {
673
+ return;
674
+ }
675
+
676
+ if ((participant.reverseOrder && participant.frameIndex - 1 <= 0) || (!participant.reverseOrder && participant.frameIndex + 1 >= track.frameCount)) {
677
+ participant.reverseOrder = !participant.reverseOrder;
678
+ }
679
+ }
680
+
681
+ if (participant.reverseOrder) {
682
+ --participant.frameIndex;
683
+ } else {
684
+ ++participant.frameIndex;
685
+ }
686
+
687
+ // Decode the next frame ahead of display so it's ready in time.
688
+ participant.imageDecoder.decode({frameIndex: participant.frameIndex})
689
+ .then( nextDecodeResult => setTimeout(_ => {
690
+ renderGif(nextDecodeResult, participant);
691
+ }, decodeResult.image.duration / 1000.0))
692
+ .catch(e => {
693
+ // We can end up requesting an imageIndex past the end since we're using
694
+ // a ReadableStrem from fetch(), when this happens just wrap around.
695
+ if (e instanceof RangeError) {
696
+ participant.frameIndex = 0;
697
+ participant.imageDecoder.decode({frameIndex: imageIndex}).then(decodeResult => renderGif(decodeResult, participant));
698
+ } else {
699
+ throw e;
700
+ }
701
+ });
702
+ }
703
+
704
+ function setParticipantsNames() {
705
+ participants.filter(p => p).forEach((participant, participantOrder) => {
706
+ if (settings.participantsNames[participantOrder]) {
707
+ participant.sdkInstance.user.updateUserSelf({lastRoomId: settings.roomId, displayname: settings.participantsNames[participantOrder]});
708
+ } else {
709
+ console.log('Not enough names');
710
+ }
711
+ });
712
+ }
713
+
714
+ function getAudioStream() {
715
+ return new Promise(resolve => {
716
+ const request = new XMLHttpRequest();
717
+ // https://ttsmp3.com/
718
+ request.open('GET', './sound_empty.mp3', true);
719
+ request.responseType = 'arraybuffer';
720
+ request.onload = function() {
721
+ audioContext.decodeAudioData(request.response)
722
+ .then(buffer => {
723
+ const streamDestination = audioContext.createMediaStreamDestination();
724
+ const source = audioContext.createBufferSource();
725
+ source.buffer = buffer;
726
+ source.connect(streamDestination);
727
+ source.loop = true;
728
+ source.start();
729
+
730
+ new Audio().srcObject = streamDestination.stream;
731
+
732
+ resolve(streamDestination.stream);
733
+ });
734
+ }
735
+ request.send();
736
+ });
737
+ }
738
+
739
+ function sparkEmoji(participant, emoji = null) {
740
+ if (emoji === null) {
741
+ emoji = sparkingEmojis[Math.floor(Math.random() * sparkingEmojis.length)];
742
+ }
743
+
744
+ participant.sdkInstance.room.sendChatMessage({
745
+ roomId: settings.roomId,
746
+ message: `:emoji${emoji}:emoji`,
747
+ senderUserId: participant.sdkInstance.userId,
748
+ options: [50],
749
+ });
750
+ }
751
+
752
+ function enableEmojiSparking(participant = null) {
753
+ (participant ? [participant] : participants.filter(p => p)).forEach(participant => {
754
+ participant.emojiSparkingTimeoutId = setTimeout(() => {
755
+ sparkEmoji(participant);
756
+ enableEmojiSparking(participant);
757
+ }, Math.round((Math.random() * (settings.emojiSparkingInterval - 5) + 5) * 1000));
758
+ });
759
+ }
760
+
761
+ function disableEmojiSparking() {
762
+ participants.filter(p => p).forEach(participant => {
763
+ clearTimeout(participant.emojiSparkingTimeoutId);
764
+ });
765
+ }
766
+
767
+ function burstEmojiSparking(participant = null, emoji = null) {
768
+ (participant ? [participant] : participants.filter(p => p)).forEach(participant => {
769
+ setTimeout(() => {
770
+ sparkEmoji(participant, emoji);
771
+ }, Math.round(Math.random() * 3000));
772
+ });
773
+ }
774
+
775
+ // ====================== Initialization ==============================
776
+
777
+ document.addEventListener("DOMContentLoaded", function() {
778
+ initializeSettings();
779
+ getSettingsFromUrl();
780
+
781
+ if (!settings.advancedSettings) {
782
+ settings.participantsHighQuality = true;
783
+ document.head.insertAdjacentHTML("beforeend", `<style>.--advanced{display:none}</style>`)
784
+ }
785
+
786
+ fillSettingsForm();
787
+ });
732
788
 
733
789
  </script>
734
790