@flashphoner/sfusdk-examples 2.0.56

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.
Files changed (40) hide show
  1. package/README.md +43 -0
  2. package/package.json +32 -0
  3. package/src/client/chat.js +67 -0
  4. package/src/client/config.json +26 -0
  5. package/src/client/controls.js +314 -0
  6. package/src/client/display.js +502 -0
  7. package/src/client/main.css +45 -0
  8. package/src/client/main.html +220 -0
  9. package/src/client/main.js +157 -0
  10. package/src/client/resources/details_close.png +0 -0
  11. package/src/client/resources/details_open.png +0 -0
  12. package/src/client/util.js +67 -0
  13. package/src/commons/js/config.js +81 -0
  14. package/src/commons/js/display.js +484 -0
  15. package/src/commons/js/util.js +202 -0
  16. package/src/commons/media/silence.mp3 +0 -0
  17. package/src/controller/dependencies/sigma/sigma.renderers.edgeLabels.min.js +1 -0
  18. package/src/controller/dependencies/sigma/sigma.renderers.parallelEdges.min.js +1 -0
  19. package/src/controller/dependencies/sigma/sigma.require.js +12076 -0
  20. package/src/controller/graph-view.js +32 -0
  21. package/src/controller/main.css +45 -0
  22. package/src/controller/main.html +79 -0
  23. package/src/controller/main.js +65 -0
  24. package/src/controller/parser.js +202 -0
  25. package/src/controller/resources/details_close.png +0 -0
  26. package/src/controller/resources/details_open.png +0 -0
  27. package/src/controller/rest.js +56 -0
  28. package/src/controller/table-view.js +64 -0
  29. package/src/controller/test-data.js +382 -0
  30. package/src/player/config.json +8 -0
  31. package/src/player/player.css +19 -0
  32. package/src/player/player.html +54 -0
  33. package/src/player/player.js +209 -0
  34. package/src/sfu.ts +28 -0
  35. package/src/two-way-streaming/config.json +34 -0
  36. package/src/two-way-streaming/two-way-streaming.css +26 -0
  37. package/src/two-way-streaming/two-way-streaming.html +72 -0
  38. package/src/two-way-streaming/two-way-streaming.js +375 -0
  39. package/tsconfig.json +15 -0
  40. package/webpack.config.js +40 -0
@@ -0,0 +1,502 @@
1
+ const initLocalDisplay = function(localDisplayElement){
2
+ const localDisplayDiv = localDisplayElement;
3
+ const localDisplays = {};
4
+
5
+ const removeLocalDisplay = function(id) {
6
+ delete localDisplays[id];
7
+ $('#' + id).remove();
8
+ reassembleLocalLayout();
9
+ }
10
+
11
+ const getAudioContainer = function() {
12
+ for (const [key, value] of Object.entries(localDisplays)) {
13
+ let video = value.getElementsByTagName("video");
14
+ if (video && video[0]) {
15
+ let audioStateButton = value.getElementsByTagName("button");
16
+ let audioTracks = video[0].srcObject.getAudioTracks();
17
+ if (!audioTracks || audioTracks.length === 0) {
18
+ return {
19
+ id: value.id,
20
+ video: video[0],
21
+ audioStateDisplay: audioStateButton[0]
22
+ }
23
+ }
24
+ }
25
+ }
26
+ };
27
+
28
+ const add = function(id, name, stream) {
29
+ if (stream.getAudioTracks().length > 0) {
30
+ let videoElement = getAudioContainer();
31
+ if (videoElement) {
32
+ let track = stream.getAudioTracks()[0];
33
+ videoElement.video.srcObject.addTrack(track);
34
+ videoElement.audioStateDisplay.innerHTML = "Audio state: " + stream.getAudioTracks()[0].enabled;
35
+ track.addEventListener("ended", function() {
36
+ videoElement.video.srcObject.removeTrack(track);
37
+ videoElement.audioStateDisplay.innerHTML = "Audio state: " + false;
38
+ //check video element has no tracks left
39
+ for (const [key, vTrack] of Object.entries(videoElement.video.srcObject.getTracks())) {
40
+ if (vTrack.readyState !== "ended") {
41
+ return;
42
+ }
43
+ }
44
+ removeLocalDisplay(videoElement.id);
45
+ });
46
+ return;
47
+ }
48
+ }
49
+
50
+ const coreDisplay = document.createElement('div');
51
+ coreDisplay.setAttribute("style","width:200px; height:auto; border: solid; border-width: 1px");
52
+ coreDisplay.id = stream.id;
53
+ const streamNameDisplay = document.createElement("div");
54
+ streamNameDisplay.innerHTML = "Name: " + name;
55
+ streamNameDisplay.setAttribute("style","width:auto; height:30px");
56
+ coreDisplay.appendChild(streamNameDisplay);
57
+
58
+ const audioStateDisplay = document.createElement("button");
59
+ audioStateDisplay.setAttribute("style","width:auto; height:30px");
60
+ audioStateDisplay.innerHTML = "Audio state: " + (stream.getAudioTracks().length > 0 ? stream.getAudioTracks()[0].enabled : false);
61
+ audioStateDisplay.addEventListener('click', function(){
62
+ if (stream.getAudioTracks().length > 0) {
63
+ stream.getAudioTracks()[0].enabled = !(stream.getAudioTracks()[0].enabled);
64
+ audioStateDisplay.innerHTML = "Audio state: " + stream.getAudioTracks()[0].enabled;
65
+ }
66
+ });
67
+ coreDisplay.appendChild(audioStateDisplay);
68
+
69
+ const streamDisplay = document.createElement('div');
70
+ streamDisplay.id = id;
71
+ streamDisplay.setAttribute("style","width:auto; height:auto");
72
+ coreDisplay.appendChild(streamDisplay);
73
+ const video = document.createElement("video");
74
+ streamDisplay.appendChild(video);
75
+ video.srcObject = stream;
76
+ video.muted = true;
77
+ video.onloadedmetadata = function (e) {
78
+ video.play();
79
+ };
80
+ stream.getTracks().forEach(function(track){
81
+ track.addEventListener("ended", function() {
82
+ video.srcObject.removeTrack(track);
83
+ //check video element has no tracks left
84
+ for (const [key, vTrack] of Object.entries(video.srcObject.getTracks())) {
85
+ if (vTrack.readyState !== "ended") {
86
+ return;
87
+ }
88
+ }
89
+ removeLocalDisplay(id);
90
+ });
91
+ });
92
+ video.addEventListener('resize', function (event) {
93
+ streamNameDisplay.innerHTML = "Name: " + name + " " + video.videoWidth + "x" + video.videoHeight;
94
+ resizeVideo(event.target);
95
+ });
96
+ localDisplays[id] = coreDisplay;
97
+ reassembleLocalLayout();
98
+ return coreDisplay;
99
+ }
100
+
101
+ const reassembleLocalLayout = function() {
102
+ let gridWidth = gridSize(Object.keys(localDisplays).length).x;
103
+ let container = document.createElement('div');
104
+ let row;
105
+ let rowI = 1;
106
+ let colI = 0;
107
+ for (const [key, value] of Object.entries(localDisplays)) {
108
+ if (row) {
109
+ if (colI >= gridWidth) {
110
+ row = createRow(container);
111
+ rowI++;
112
+ colI = 0;
113
+ }
114
+ } else {
115
+ row = createRow(container);
116
+ }
117
+ $("#" + key).detach();
118
+ let col = createCol(row);
119
+ col.appendChild(value);
120
+ colI++;
121
+ }
122
+ $(localDisplayDiv).empty();
123
+ localDisplayDiv.appendChild(container);
124
+ }
125
+
126
+ return {
127
+ add: add
128
+ }
129
+ }
130
+
131
+ const initRemoteDisplay = function(room, mainDiv, peerConnection) {
132
+ const constants = SFU.constants;
133
+ const remoteParticipants = {};
134
+ room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) {
135
+ let participant = remoteParticipants[e.info.nickName];
136
+ if (!participant) {
137
+ participant = {};
138
+ participant.nickName = e.info.nickName;
139
+ participant.tracks = [];
140
+ participant.displays = [];
141
+ remoteParticipants[participant.nickName] = participant;
142
+ }
143
+ participant.tracks.push.apply(participant.tracks, e.info.info);
144
+ for (const pTrack of e.info.info) {
145
+ let createDisplay = true;
146
+ for (let i = 0; i < participant.displays.length; i++) {
147
+ let display = participant.displays[i];
148
+ if (pTrack.type === "VIDEO") {
149
+ if (display.hasVideo()) {
150
+ continue;
151
+ }
152
+ display.videoMid = pTrack.mid;
153
+ display.setTrackInfo(pTrack);
154
+ createDisplay = false;
155
+ break;
156
+ } else if (pTrack.type === "AUDIO") {
157
+ if (display.hasAudio()) {
158
+ continue;
159
+ }
160
+ display.audioMid = pTrack.mid;
161
+ createDisplay = false;
162
+ break;
163
+ }
164
+ }
165
+ if (!createDisplay) {
166
+ continue;
167
+ }
168
+ let display = createRemoteDisplay(participant.nickName, participant.nickName, mainDiv);
169
+ participant.displays.push(display);
170
+ if (pTrack.type === "VIDEO") {
171
+ display.videoMid = pTrack.mid;
172
+ display.setTrackInfo(pTrack);
173
+ } else if (pTrack.type === "AUDIO") {
174
+ display.audioMid = pTrack.mid;
175
+ }
176
+ }
177
+ }).on(constants.SFU_ROOM_EVENT.REMOVE_TRACKS, function(e) {
178
+ const participant = remoteParticipants[e.info.nickName];
179
+ if (!participant) {
180
+ return;
181
+ }
182
+ for (const rTrack of e.info.info) {
183
+ for (let i = 0; i < participant.tracks.length; i++) {
184
+ if (rTrack.mid === participant.tracks[i].mid) {
185
+ participant.tracks.splice(i, 1);
186
+ break;
187
+ }
188
+ }
189
+ for (let i = 0; i < participant.displays.length; i++) {
190
+ let found = false;
191
+ const display = participant.displays[i];
192
+ if (display.audioMid === rTrack.mid) {
193
+ display.setAudio(null);
194
+ found = true;
195
+ } else if (display.videoMid === rTrack.mid) {
196
+ display.setVideo(null);
197
+ found = true;
198
+ }
199
+ if (found) {
200
+ if (!display.hasAudio() && !display.hasVideo()) {
201
+ display.dispose();
202
+ participant.displays.splice(i, 1);
203
+ }
204
+ break;
205
+ }
206
+ }
207
+ }
208
+ }).on(constants.SFU_ROOM_EVENT.LEFT, function(e) {
209
+ let participant = remoteParticipants[e.name];
210
+ if (!participant) {
211
+ return;
212
+ }
213
+ participant.displays.forEach(function(display){
214
+ display.dispose();
215
+ })
216
+ delete remoteParticipants[e.name];
217
+ }).on(constants.SFU_ROOM_EVENT.TRACK_QUALITY_STATE, function(e){
218
+ console.log("Received track quality state");
219
+ const participant = remoteParticipants[e.info.nickName];
220
+ if (!participant) {
221
+ return;
222
+ }
223
+
224
+ for (const rTrack of e.info.tracks) {
225
+ const mid = rTrack.mid;
226
+ for (let i = 0; i < participant.displays.length; i++) {
227
+ const display = participant.displays[i];
228
+ if (display.videoMid === mid) {
229
+ display.updateQualityInfo(rTrack.quality);
230
+ break;
231
+ }
232
+ }
233
+ }
234
+ });
235
+
236
+ const createRemoteDisplay = function(id, name, mainDiv) {
237
+ const cell = document.createElement("div");
238
+ cell.setAttribute("class", "grid-item");
239
+ cell.id = id;
240
+ mainDiv.appendChild(cell);
241
+ const streamNameDisplay = document.createElement("div");
242
+ streamNameDisplay.innerHTML = "Name: " + name;
243
+ streamNameDisplay.setAttribute("style","width:auto; height:20px");
244
+ cell.appendChild(streamNameDisplay);
245
+ const qualityDisplay = document.createElement("div");
246
+ qualityDisplay.setAttribute("style","width:auto; height:20px");
247
+ cell.appendChild(qualityDisplay);
248
+ const tidDisplay = document.createElement("div");
249
+ tidDisplay.setAttribute("style","width:auto; height:20px");
250
+ cell.appendChild(tidDisplay);
251
+
252
+ let qualityDivs = [];
253
+ let tidDivs = [];
254
+
255
+ const rootDisplay = document.createElement("div");
256
+ rootDisplay.setAttribute("style","width:auto; height:auto");
257
+ cell.appendChild(rootDisplay);
258
+ const streamDisplay = document.createElement("div");
259
+ streamDisplay.setAttribute("style","width:auto; height:auto");
260
+ rootDisplay.appendChild(streamDisplay);
261
+
262
+ let audio = null;
263
+ let video = null;
264
+ return {
265
+ dispose: function() {
266
+ cell.remove();
267
+ },
268
+ hide: function(value) {
269
+ if (value) {
270
+ cell.style.display = "none";
271
+ } else {
272
+ cell.style.display = "block";
273
+ }
274
+ },
275
+ setAudio: function(stream) {
276
+ if (audio) {
277
+ audio.remove();
278
+ }
279
+ if (!stream) {
280
+ audio = null;
281
+ this.audioMid = undefined;
282
+ return;
283
+ }
284
+ audio = document.createElement("audio");
285
+ audio.controls = "controls";
286
+ cell.appendChild(audio);
287
+ audio.srcObject = stream;
288
+ audio.play();
289
+ },
290
+ hasAudio: function() {
291
+ return audio !== null || this.audioMid !== undefined;
292
+ },
293
+ setVideo: function(stream) {
294
+ if (video) {
295
+ video.remove();
296
+ }
297
+
298
+ if (stream == null) {
299
+ video = null;
300
+ this.videoMid = undefined;
301
+ qualityDivs.forEach(function(div) {
302
+ div.remove();
303
+ });
304
+ qualityDivs = [];
305
+ tidDivs.forEach(function(div) {
306
+ div.remove();
307
+ });
308
+ tidDivs = [];
309
+ return;
310
+ }
311
+ video = document.createElement("video");
312
+ streamDisplay.appendChild(video);
313
+ video.srcObject = stream;
314
+ video.onloadedmetadata = function (e) {
315
+ video.play();
316
+ };
317
+ video.addEventListener("resize", function (event) {
318
+ streamNameDisplay.innerHTML = "Name: " + name + " " + video.videoWidth + "x" + video.videoHeight;
319
+ resizeVideo(event.target);
320
+ });
321
+ },
322
+ setTrackInfo: function(trackInfo) {
323
+ if (trackInfo && trackInfo.quality) {
324
+ for (let i = 0; i < trackInfo.quality.length; i++) {
325
+ const qualityDiv = document.createElement("button");
326
+ qualityDivs.push(qualityDiv);
327
+ qualityDiv.innerText = trackInfo.quality[i];
328
+ qualityDiv.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
329
+ qualityDiv.style.color = "red";
330
+ qualityDiv.addEventListener('click', function(){
331
+ console.log("Clicked on quality " + trackInfo.quality[i] + " trackId " + trackInfo.id);
332
+ if (qualityDiv.style.color === "red") {
333
+ return;
334
+ }
335
+ for (let c = 0; c < qualityDivs.length; c++) {
336
+ if (qualityDivs[c].style.color !== "red") {
337
+ qualityDivs[c].style.color = "gray";
338
+ }
339
+ }
340
+ qualityDiv.style.color = "blue";
341
+ room.changeQuality(trackInfo.id, trackInfo.quality[i]);
342
+ });
343
+ qualityDisplay.appendChild(qualityDiv);
344
+ }
345
+ for (let i = 0; i < 3; i++) {
346
+ const tidDiv = document.createElement("button");
347
+ tidDivs.push(tidDiv);
348
+ tidDiv.innerText = "TID"+i;
349
+ tidDiv.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
350
+ tidDiv.style.color = "gray";
351
+ tidDiv.addEventListener('click', function(){
352
+ console.log("Clicked on TID " + i + " trackId " + trackInfo.id);
353
+ for (let c = 0; c < tidDivs.length; c++) {
354
+ tidDivs[c].style.color = "gray";
355
+ }
356
+ tidDiv.style.color = "blue";
357
+ room.changeQuality(trackInfo.id, null, i);
358
+ });
359
+ tidDisplay.appendChild(tidDiv);
360
+ }
361
+ }
362
+ },
363
+ updateQualityInfo: function(videoQuality) {
364
+ for (const qualityInfo of videoQuality) {
365
+ for (const qualityDiv of qualityDivs) {
366
+ if (qualityDiv.innerText === qualityInfo.quality){
367
+ if (qualityInfo.available === true) {
368
+ qualityDiv.style.color = "gray";
369
+ } else {
370
+ qualityDiv.style.color = "red";
371
+ }
372
+ break;
373
+ }
374
+ }
375
+ }
376
+ },
377
+ hasVideo: function() {
378
+ return video !== null || this.videoMid !== undefined;
379
+ },
380
+ audioMid: undefined,
381
+ videoMid: undefined
382
+ };
383
+ };
384
+
385
+ peerConnection.ontrack = ({transceiver}) => {
386
+ let rParticipant;
387
+ console.log("Attach remote track " + transceiver.receiver.track.id + " kind " + transceiver.receiver.track.kind + " mid " + transceiver.mid);
388
+ for (const [nickName, participant] of Object.entries(remoteParticipants)) {
389
+ for (const pTrack of participant.tracks) {
390
+ console.log("Participant " + participant.nickName + " track " + pTrack.id + " mid " + pTrack.mid);
391
+ if (pTrack.mid === transceiver.mid) {
392
+ rParticipant = participant;
393
+ break;
394
+ }
395
+ }
396
+ if (rParticipant) {
397
+ break;
398
+ }
399
+ }
400
+ if (rParticipant) {
401
+ for (const display of rParticipant.displays) {
402
+ if (transceiver.receiver.track.kind === "video") {
403
+ if (display.videoMid === transceiver.mid) {
404
+ let stream = new MediaStream();
405
+ stream.addTrack(transceiver.receiver.track);
406
+ display.setVideo(stream);
407
+ break;
408
+ }
409
+ } else if (transceiver.receiver.track.kind === "audio") {
410
+ if (display.audioMid === transceiver.mid) {
411
+ let stream = new MediaStream();
412
+ stream.addTrack(transceiver.receiver.track);
413
+ display.setAudio(stream);
414
+ break;
415
+ }
416
+ }
417
+ }
418
+ } else {
419
+ console.warn("Failed to find participant for track " + transceiver.receiver.track.id);
420
+ }
421
+ }
422
+ }
423
+
424
+ const createRow = function(container) {
425
+ const row = document.createElement('div');
426
+ row.setAttribute("class","row");
427
+ container.appendChild(row);
428
+ return row;
429
+ }
430
+
431
+ const createCol = function(row) {
432
+ const col = document.createElement('div');
433
+ col.setAttribute("class","col local-video-display");
434
+ row.appendChild(col);
435
+ return col;
436
+ }
437
+
438
+ const gridSize = function(frames) {
439
+ let x = 1;
440
+ let y = 1;
441
+ let fSqrt = Math.sqrt(frames);
442
+ if (fSqrt % 1 === 0) {
443
+ x = y = Math.sqrt(frames);
444
+ } else {
445
+ x = Math.ceil(fSqrt);
446
+ while(x * y < frames) {
447
+ y++;
448
+ }
449
+ }
450
+ return {
451
+ x: x,
452
+ y: y
453
+ }
454
+ }
455
+
456
+ const resizeVideo = function(video, width, height) {
457
+ if (!video.parentNode) {
458
+ return;
459
+ }
460
+ if (video instanceof HTMLCanvasElement) {
461
+ video.videoWidth = video.width;
462
+ video.videoHeight = video.height;
463
+ }
464
+ var display = video.parentNode;
465
+ var parentSize = {
466
+ w: display.parentNode.clientWidth,
467
+ h: display.parentNode.clientHeight
468
+ };
469
+ var newSize;
470
+ if (width && height) {
471
+ newSize = downScaleToFitSize(width, height, parentSize.w, parentSize.h);
472
+ } else {
473
+ newSize = downScaleToFitSize(video.videoWidth, video.videoHeight, parentSize.w, parentSize.h);
474
+ }
475
+ display.style.width = newSize.w + "px";
476
+ display.style.height = newSize.h + "px";
477
+
478
+ //vertical align
479
+ var margin = 0;
480
+ if (parentSize.h - newSize.h > 1) {
481
+ margin = Math.floor((parentSize.h - newSize.h) / 2);
482
+ }
483
+ display.style.margin = margin + "px auto";
484
+ console.log("Resize from " + video.videoWidth + "x" + video.videoHeight + " to " + display.offsetWidth + "x" + display.offsetHeight);
485
+ }
486
+
487
+ const downScaleToFitSize = function(videoWidth, videoHeight, dstWidth, dstHeight) {
488
+ var newWidth, newHeight;
489
+ var videoRatio = videoWidth / videoHeight;
490
+ var dstRatio = dstWidth / dstHeight;
491
+ if (dstRatio > videoRatio) {
492
+ newHeight = dstHeight;
493
+ newWidth = Math.floor(videoRatio * dstHeight);
494
+ } else {
495
+ newWidth = dstWidth;
496
+ newHeight = Math.floor(dstWidth / videoRatio);
497
+ }
498
+ return {
499
+ w: newWidth,
500
+ h: newHeight
501
+ };
502
+ }
@@ -0,0 +1,45 @@
1
+ .grid-container {
2
+ display: grid;
3
+ grid-template-columns: auto auto auto;
4
+ width: 1284px;
5
+ height: auto;
6
+ }
7
+ .grid-container-local {
8
+ display: grid;
9
+ grid-template-columns: auto auto;
10
+ width: 1284px;
11
+ height: auto;
12
+ }
13
+ .grid-item {
14
+ border: 1px solid rgba(0, 0, 0, 0.8);
15
+ text-align: center;
16
+ width: 428px;
17
+ height: auto;
18
+ }
19
+
20
+ video, object {
21
+ width: 100%;
22
+ height: 100%;
23
+ }
24
+
25
+ .local-video-display {
26
+ border: 1px solid rgba(0, 0, 0, 0.8);
27
+ text-align: center;
28
+ width: 200px;
29
+ height: auto;
30
+ }
31
+
32
+ #messages {
33
+ height: 400px;
34
+ overflow-y: auto;
35
+ overflow-wrap: break-word;
36
+ word-break: break-all;
37
+ }
38
+
39
+ td.details-control {
40
+ background: url('resources/details_open.png') no-repeat center center;
41
+ cursor: pointer;
42
+ }
43
+ tr.shown td.details-control {
44
+ background: url('resources/details_close.png') no-repeat center center;
45
+ }