@flashphoner/sfusdk-examples 2.0.244 → 2.0.249
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/package.json +2 -2
- package/src/client/chat.js +15 -1
- package/src/client/config.json +2 -2
- package/src/client/controls.js +4 -2
- package/src/client/main.html +3 -2
- package/src/client/main.js +49 -47
- package/src/commons/js/display.js +1481 -635
- package/src/commons/js/util.js +4 -0
- package/src/player/player.js +3 -7
- package/src/track-test/track-test.css +26 -0
- package/src/track-test/track-test.html +89 -0
- package/src/track-test/track-test.js +646 -0
- package/src/two-way-streaming/config.json +3 -3
- package/src/two-way-streaming/two-way-streaming.js +86 -89
- package/src/webrtc-abr-player/player.js +16 -23
- package/src/client/display.js +0 -502
- package/src/client/util.js +0 -67
|
@@ -8,11 +8,11 @@ const QUALITY_COLORS = {
|
|
|
8
8
|
SELECTED: "blue"
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
const initLocalDisplay = function(localDisplayElement){
|
|
11
|
+
const initLocalDisplay = function (localDisplayElement) {
|
|
12
12
|
const localDisplayDiv = localDisplayElement;
|
|
13
13
|
const localDisplays = {};
|
|
14
14
|
|
|
15
|
-
const removeLocalDisplay = function(id) {
|
|
15
|
+
const removeLocalDisplay = function (id) {
|
|
16
16
|
let localDisplay = document.getElementById(localDisplays[id].id);
|
|
17
17
|
let video = localDisplay.getElementsByTagName("video");
|
|
18
18
|
if (video && video[0]) {
|
|
@@ -24,7 +24,7 @@ const initLocalDisplay = function(localDisplayElement){
|
|
|
24
24
|
localDisplay.remove();
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const getAudioContainer = function() {
|
|
27
|
+
const getAudioContainer = function () {
|
|
28
28
|
for (const [key, value] of Object.entries(localDisplays)) {
|
|
29
29
|
let video = value.getElementsByTagName("video");
|
|
30
30
|
if (video && video[0]) {
|
|
@@ -41,24 +41,24 @@ const initLocalDisplay = function(localDisplayElement){
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const onMuteClick = function(button, stream, type) {
|
|
44
|
+
const onMuteClick = function (button, stream, type) {
|
|
45
45
|
if (stream.getAudioTracks().length > 0) {
|
|
46
46
|
stream.getAudioTracks()[0].enabled = !(stream.getAudioTracks()[0].enabled);
|
|
47
47
|
button.innerHTML = audioStateText(stream) + " " + type;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const add = function(id, name, stream, type) {
|
|
51
|
+
const add = function (id, name, stream, type) {
|
|
52
52
|
if (stream.getAudioTracks().length > 0) {
|
|
53
53
|
let videoElement = getAudioContainer();
|
|
54
54
|
if (videoElement) {
|
|
55
55
|
let track = stream.getAudioTracks()[0];
|
|
56
56
|
videoElement.video.srcObject.addTrack(track);
|
|
57
57
|
videoElement.audioStateDisplay.innerHTML = audioStateText(stream) + " " + type;
|
|
58
|
-
videoElement.audioStateDisplay.addEventListener("click", function() {
|
|
58
|
+
videoElement.audioStateDisplay.addEventListener("click", function () {
|
|
59
59
|
onMuteClick(videoElement.audioStateDisplay, stream, type);
|
|
60
60
|
});
|
|
61
|
-
track.addEventListener("ended", function() {
|
|
61
|
+
track.addEventListener("ended", function () {
|
|
62
62
|
videoElement.video.srcObject.removeTrack(track);
|
|
63
63
|
videoElement.audioStateDisplay.innerHTML = "No audio";
|
|
64
64
|
//check video element has no tracks left
|
|
@@ -78,13 +78,14 @@ const initLocalDisplay = function(localDisplayElement){
|
|
|
78
78
|
const publisherNameDisplay = createInfoDisplay(coreDisplay, name + " " + type);
|
|
79
79
|
|
|
80
80
|
const audioStateDisplay = document.createElement("button");
|
|
81
|
+
audioStateDisplay.innerText = audioStateText();
|
|
81
82
|
coreDisplay.appendChild(audioStateDisplay);
|
|
82
83
|
|
|
83
84
|
const streamDisplay = createContainer(coreDisplay);
|
|
84
85
|
streamDisplay.id = "stream-" + id;
|
|
85
86
|
const video = document.createElement("video");
|
|
86
87
|
video.muted = true;
|
|
87
|
-
if(Browser().isSafariWebRTC()) {
|
|
88
|
+
if (Browser().isSafariWebRTC()) {
|
|
88
89
|
video.setAttribute("playsinline", "");
|
|
89
90
|
video.setAttribute("webkit-playsinline", "");
|
|
90
91
|
}
|
|
@@ -93,8 +94,8 @@ const initLocalDisplay = function(localDisplayElement){
|
|
|
93
94
|
video.onloadedmetadata = function (e) {
|
|
94
95
|
video.play();
|
|
95
96
|
};
|
|
96
|
-
stream.getTracks().forEach(function(track){
|
|
97
|
-
track.addEventListener("ended", function() {
|
|
97
|
+
stream.getTracks().forEach(function (track) {
|
|
98
|
+
track.addEventListener("ended", function () {
|
|
98
99
|
video.srcObject.removeTrack(track);
|
|
99
100
|
//check video element has no tracks left
|
|
100
101
|
for (const [key, vTrack] of Object.entries(video.srcObject.getTracks())) {
|
|
@@ -116,7 +117,7 @@ const initLocalDisplay = function(localDisplayElement){
|
|
|
116
117
|
hideItem(streamDisplay);
|
|
117
118
|
// Set up mute button for audio only stream
|
|
118
119
|
audioStateDisplay.innerHTML = audioStateText(stream) + " " + type;
|
|
119
|
-
audioStateDisplay.addEventListener("click", function() {
|
|
120
|
+
audioStateDisplay.addEventListener("click", function () {
|
|
120
121
|
onMuteClick(audioStateDisplay, stream, type);
|
|
121
122
|
});
|
|
122
123
|
}
|
|
@@ -132,7 +133,7 @@ const initLocalDisplay = function(localDisplayElement){
|
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
const audioStateText = function (stream) {
|
|
135
|
-
if (stream.getAudioTracks().length > 0) {
|
|
136
|
+
if (stream && stream.getAudioTracks().length > 0) {
|
|
136
137
|
if (stream.getAudioTracks()[0].enabled) {
|
|
137
138
|
return "Mute";
|
|
138
139
|
} else {
|
|
@@ -148,687 +149,1532 @@ const initLocalDisplay = function(localDisplayElement){
|
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
152
|
+
const abrManagerFactory = function (room, abrOptions) {
|
|
153
|
+
return {
|
|
154
|
+
createAbrManager: function () {
|
|
155
|
+
let abr = {
|
|
156
|
+
track: null,
|
|
157
|
+
interval: abrOptions.interval,
|
|
158
|
+
thresholds: abrOptions.thresholds,
|
|
159
|
+
qualities: [],
|
|
160
|
+
currentQualityName: null,
|
|
161
|
+
statTimer: null,
|
|
162
|
+
paused: false,
|
|
163
|
+
manual: false,
|
|
164
|
+
keepGoodTimeout: abrOptions.abrKeepOnGoodQuality,
|
|
165
|
+
keepGoodTimer: null,
|
|
166
|
+
tryUpperTimeout: abrOptions.abrTryForUpperQuality,
|
|
167
|
+
tryUpperTimer: null,
|
|
168
|
+
start: function () {
|
|
169
|
+
this.stop();
|
|
170
|
+
console.log("Start abr interval")
|
|
171
|
+
if (abr.interval) {
|
|
172
|
+
const thresholds = Thresholds();
|
|
173
|
+
for (const threshold of abr.thresholds) {
|
|
174
|
+
thresholds.add(threshold.parameter, threshold.maxLeap);
|
|
175
|
+
}
|
|
176
|
+
abr.statsTimer = setInterval(() => {
|
|
177
|
+
if (abr.track) {
|
|
178
|
+
room.getStats(abr.track.track, constants.SFU_RTC_STATS_TYPE.INBOUND, (stats) => {
|
|
179
|
+
if (thresholds.isReached(stats)) {
|
|
180
|
+
abr.shiftDown();
|
|
181
|
+
} else {
|
|
182
|
+
abr.useGoodQuality();
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}, abr.interval);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
stop: function () {
|
|
190
|
+
console.log("Stop abr interval")
|
|
191
|
+
abr.stopKeeping();
|
|
192
|
+
abr.stopTrying();
|
|
193
|
+
if (abr.statsTimer) {
|
|
194
|
+
clearInterval(abr.statsTimer);
|
|
195
|
+
abr.statsTimer = null;
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
isEnabled: function () {
|
|
199
|
+
return (abr.interval > 0);
|
|
200
|
+
},
|
|
201
|
+
pause: function () {
|
|
202
|
+
abr.paused = true;
|
|
203
|
+
},
|
|
204
|
+
resume: function () {
|
|
205
|
+
abr.paused = false;
|
|
206
|
+
},
|
|
207
|
+
setAuto: function () {
|
|
208
|
+
abr.manual = false;
|
|
209
|
+
abr.resume();
|
|
210
|
+
},
|
|
211
|
+
setManual: function () {
|
|
212
|
+
abr.manual = true;
|
|
213
|
+
abr.pause();
|
|
214
|
+
},
|
|
215
|
+
isAuto: function () {
|
|
216
|
+
return !abr.manual;
|
|
217
|
+
},
|
|
218
|
+
setTrack: function (track) {
|
|
219
|
+
abr.track = track;
|
|
220
|
+
},
|
|
221
|
+
setQualitiesList: function (qualities) {
|
|
222
|
+
abr.qualities = qualities;
|
|
223
|
+
},
|
|
224
|
+
clearQualityState: function () {
|
|
225
|
+
abr.qualities = [];
|
|
226
|
+
abr.currentQualityName = null;
|
|
227
|
+
},
|
|
228
|
+
addQuality: function (name) {
|
|
229
|
+
abr.qualities.push({name: name, available: false, good: true});
|
|
230
|
+
},
|
|
231
|
+
setQualityAvailable: function (name, available) {
|
|
232
|
+
for (let i = 0; i < abr.qualities.length; i++) {
|
|
233
|
+
if (name === abr.qualities[i].name) {
|
|
234
|
+
abr.qualities[i].available = available;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
setQualityGood: function (name, good) {
|
|
239
|
+
if (name) {
|
|
240
|
+
for (let i = 0; i < abr.qualities.length; i++) {
|
|
241
|
+
if (name === abr.qualities[i].name) {
|
|
242
|
+
abr.qualities[i].good = good;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
getFirstAvailableQuality: function () {
|
|
248
|
+
for (let i = 0; i < abr.qualities.length; i++) {
|
|
249
|
+
if (abr.qualities[i].available) {
|
|
250
|
+
return abr.qualities[i];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
},
|
|
255
|
+
getLowerQuality: function (name) {
|
|
256
|
+
let quality = null;
|
|
257
|
+
if (!name) {
|
|
258
|
+
// There were no switching yet, return a first available quality
|
|
259
|
+
return abr.getFirstAvailableQuality();
|
|
260
|
+
}
|
|
261
|
+
let currentIndex = abr.qualities.map(item => item.name).indexOf(name);
|
|
262
|
+
for (let i = 0; i < currentIndex; i++) {
|
|
263
|
+
if (abr.qualities[i].available) {
|
|
264
|
+
quality = abr.qualities[i];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return quality;
|
|
268
|
+
},
|
|
269
|
+
getUpperQuality: function (name) {
|
|
270
|
+
let quality = null;
|
|
271
|
+
if (!name) {
|
|
272
|
+
// There were no switching yet, return a first available quality
|
|
273
|
+
return abr.getFirstAvailableQuality();
|
|
274
|
+
}
|
|
275
|
+
let currentIndex = abr.qualities.map(item => item.name).indexOf(name);
|
|
276
|
+
for (let i = currentIndex + 1; i < abr.qualities.length; i++) {
|
|
277
|
+
if (abr.qualities[i].available) {
|
|
278
|
+
quality = abr.qualities[i];
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return quality;
|
|
283
|
+
},
|
|
284
|
+
shiftDown: function () {
|
|
285
|
+
if (!abr.manual && !abr.paused) {
|
|
286
|
+
abr.stopKeeping();
|
|
287
|
+
abr.setQualityGood(abr.currentQualityName, false);
|
|
288
|
+
let quality = abr.getLowerQuality(abr.currentQualityName);
|
|
289
|
+
if (quality) {
|
|
290
|
+
console.log("Switching down to " + quality.name + " quality");
|
|
291
|
+
abr.setQuality(quality.name);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
shiftUp: function () {
|
|
296
|
+
if (!abr.manual && !abr.paused) {
|
|
297
|
+
let quality = abr.getUpperQuality(abr.currentQualityName);
|
|
298
|
+
if (quality) {
|
|
299
|
+
if (quality.good) {
|
|
300
|
+
console.log("Switching up to " + quality.name + " quality");
|
|
301
|
+
abr.setQuality(quality.name);
|
|
302
|
+
} else {
|
|
303
|
+
abr.tryUpper();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
useGoodQuality: function () {
|
|
309
|
+
if (!abr.manual && !abr.paused) {
|
|
310
|
+
if (!abr.currentQualityName) {
|
|
311
|
+
let quality = abr.getFirstAvailableQuality();
|
|
312
|
+
abr.currentQualityName = quality.name;
|
|
313
|
+
}
|
|
314
|
+
abr.setQualityGood(abr.currentQualityName, true);
|
|
315
|
+
abr.keepGoodQuality();
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
keepGoodQuality: function () {
|
|
319
|
+
if (abr.keepGoodTimeout && !abr.keepGoodTimer && abr.getUpperQuality(abr.currentQualityName)) {
|
|
320
|
+
console.log("start keepGoodTimer");
|
|
321
|
+
abr.keepGoodTimer = setTimeout(() => {
|
|
322
|
+
abr.shiftUp();
|
|
323
|
+
abr.stopKeeping();
|
|
324
|
+
}, abr.keepGoodTimeout);
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
stopKeeping: function () {
|
|
328
|
+
if (abr.keepGoodTimer) {
|
|
329
|
+
clearTimeout(abr.keepGoodTimer);
|
|
330
|
+
abr.keepGoodTimer = null;
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
tryUpper: function () {
|
|
334
|
+
let quality = abr.getUpperQuality(abr.currentQualityName);
|
|
335
|
+
if (abr.tryUpperTimeout && !abr.tryUpperTimer && quality) {
|
|
336
|
+
abr.tryUpperTimer = setTimeout(() => {
|
|
337
|
+
abr.setQualityGood(quality.name, true);
|
|
338
|
+
abr.stopTrying();
|
|
339
|
+
}, abr.tryUpperTimeout);
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
stopTrying: function () {
|
|
343
|
+
if (abr.tryUpperTimer) {
|
|
344
|
+
clearTimeout(abr.tryUpperTimer);
|
|
345
|
+
abr.tryUpperTimer = null;
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
setQuality: async function (name) {
|
|
349
|
+
console.log("set quality name");
|
|
350
|
+
// Pause switching until a new quality is received
|
|
351
|
+
abr.pause();
|
|
352
|
+
abr.currentQualityName = name;
|
|
353
|
+
abr.track.setPreferredQuality(abr.currentQualityName);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return abr;
|
|
221
357
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
const createDefaultMeetingModel = function (meetingView, participantFactory, displayOptions, abrFactory) {
|
|
363
|
+
return {
|
|
364
|
+
participants: new Map(),
|
|
365
|
+
meetingName: null,
|
|
366
|
+
addParticipant: function (userId, participantName) {
|
|
367
|
+
if (this.participants.get(userId)) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const [participantModel, participantView, participant] = participantFactory.createParticipant(userId, participantName, displayOptions, abrFactory);
|
|
371
|
+
this.participants.set(userId, participant);
|
|
372
|
+
meetingView.addParticipant(userId, participantName, participantView.rootDiv);
|
|
373
|
+
},
|
|
374
|
+
removeParticipant: function (userId) {
|
|
375
|
+
const participant = this.participants.get(userId);
|
|
376
|
+
if (participant) {
|
|
377
|
+
this.participants.delete(userId);
|
|
378
|
+
meetingView.removeParticipant(userId);
|
|
379
|
+
participant.dispose();
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
renameParticipant: function (userId, newNickname) {
|
|
383
|
+
const participant = this.participants.get(userId);
|
|
384
|
+
if (participant) {
|
|
385
|
+
participant.setNickname(newNickname);
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
addTracks: function (userId, tracks) {
|
|
389
|
+
const participant = this.participants.get(userId);
|
|
390
|
+
if (!participant) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
for (const track of tracks) {
|
|
395
|
+
if (track.type === "VIDEO") {
|
|
396
|
+
participant.addVideoTrack(track);
|
|
397
|
+
} else if (track.type === "AUDIO") {
|
|
398
|
+
participant.addAudioTrack(track);
|
|
227
399
|
}
|
|
228
400
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
401
|
+
},
|
|
402
|
+
removeTracks: function (userId, tracks) {
|
|
403
|
+
const participant = this.participants.get(userId);
|
|
404
|
+
if (!participant) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
for (const track of tracks) {
|
|
408
|
+
if (track.type === "VIDEO") {
|
|
409
|
+
participant.removeVideoTrack(track);
|
|
410
|
+
} else if (track.type === "AUDIO") {
|
|
411
|
+
participant.removeAudioTrack(track);
|
|
238
412
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
updateQualityInfo: function (userId, tracksInfo) {
|
|
416
|
+
const participant = this.participants.get(userId);
|
|
417
|
+
if (!participant) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
participant.updateQualityInfo(tracksInfo);
|
|
421
|
+
},
|
|
422
|
+
end: function () {
|
|
423
|
+
console.log("Meeting " + this.meetingName + " ended")
|
|
424
|
+
meetingView.end();
|
|
425
|
+
this.participants.forEach((participant, id) => {
|
|
426
|
+
participant.dispose();
|
|
427
|
+
});
|
|
428
|
+
this.participants.clear();
|
|
429
|
+
},
|
|
430
|
+
setMeetingName: function (id) {
|
|
431
|
+
this.meetingName = id;
|
|
432
|
+
meetingView.setMeetingName(id);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const createDefaultMeetingView = function (entryPoint) {
|
|
438
|
+
const rootDiv = document.createElement("div");
|
|
439
|
+
rootDiv.setAttribute("class", "grid-item");
|
|
440
|
+
entryPoint.appendChild(rootDiv);
|
|
441
|
+
const title = document.createElement("label");
|
|
442
|
+
title.setAttribute("style", "display:block; border: solid; border-width: 1px");
|
|
443
|
+
rootDiv.appendChild(title);
|
|
444
|
+
return {
|
|
445
|
+
participantViews: new Map(),
|
|
446
|
+
setMeetingName: function (id) {
|
|
447
|
+
title.innerText = "Meeting: " + id;
|
|
448
|
+
},
|
|
449
|
+
addParticipant: function (userId, participantName, cell) {
|
|
450
|
+
const participantDiv = createContainer(rootDiv);
|
|
451
|
+
participantDiv.appendChild(cell);
|
|
452
|
+
this.participantViews.set(userId, participantDiv);
|
|
453
|
+
},
|
|
454
|
+
removeParticipant: function (userId) {
|
|
455
|
+
const cell = this.participantViews.get(userId);
|
|
456
|
+
if (cell) {
|
|
457
|
+
this.participantViews.delete(userId);
|
|
458
|
+
cell.remove();
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
end: function () {
|
|
462
|
+
rootDiv.remove();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const oneToOneParticipantFactory = function (remoteTrackFactory) {
|
|
467
|
+
return createParticipantFactory(remoteTrackFactory, createOneToOneParticipantView, createOneToOneParticipantModel);
|
|
468
|
+
}
|
|
469
|
+
const createParticipantFactory = function (remoteTrackFactory, createParticipantView, createParticipantModel) {
|
|
470
|
+
return {
|
|
471
|
+
displayOptions: null,
|
|
472
|
+
abrFactory: null,
|
|
473
|
+
createParticipant: function (userId, nickname) {
|
|
474
|
+
const view = createParticipantView();
|
|
475
|
+
const model = createParticipantModel(userId, nickname, view, remoteTrackFactory, this.abrFactory, this.displayOptions);
|
|
476
|
+
const controller = createParticipantController(model);
|
|
477
|
+
return [model, view, controller];
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const createParticipantController = function (model) {
|
|
483
|
+
return {
|
|
484
|
+
addVideoTrack: function (track) {
|
|
485
|
+
model.addVideoTrack(track);
|
|
486
|
+
},
|
|
487
|
+
removeVideoTrack: function (track) {
|
|
488
|
+
model.removeVideoTrack(track);
|
|
489
|
+
},
|
|
490
|
+
addAudioTrack: function (track) {
|
|
491
|
+
model.addAudioTrack(track);
|
|
492
|
+
},
|
|
493
|
+
removeAudioTrack: function (track) {
|
|
494
|
+
model.removeAudioTrack(track);
|
|
495
|
+
},
|
|
496
|
+
updateQualityInfo: function (qualityInfo) {
|
|
497
|
+
model.updateQualityInfo(qualityInfo);
|
|
498
|
+
},
|
|
499
|
+
setNickname: function (nickname) {
|
|
500
|
+
model.setNickname(nickname);
|
|
501
|
+
},
|
|
502
|
+
dispose: function () {
|
|
503
|
+
model.dispose();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const createOneToManyParticipantView = function () {
|
|
509
|
+
|
|
510
|
+
const participantDiv = createContainer(null);
|
|
511
|
+
|
|
512
|
+
const audioDisplay = createContainer(participantDiv);
|
|
513
|
+
|
|
514
|
+
const participantNicknameDisplay = createInfoDisplay(participantDiv, "Name: ")
|
|
515
|
+
|
|
516
|
+
const audioElements = new Map();
|
|
517
|
+
const player = createVideoPlayer(participantDiv);
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
rootDiv: participantDiv,
|
|
521
|
+
currentTrack: null,
|
|
522
|
+
dispose: function () {
|
|
523
|
+
player.dispose();
|
|
524
|
+
for (const element of audioElements.values()) {
|
|
525
|
+
element.remove();
|
|
526
|
+
}
|
|
527
|
+
audioElements.clear();
|
|
528
|
+
},
|
|
529
|
+
addVideoTrack: function (track, requestVideoTrack) {
|
|
530
|
+
player.addVideoTrack(track, async () => {
|
|
531
|
+
return requestVideoTrack();
|
|
532
|
+
});
|
|
533
|
+
},
|
|
534
|
+
removeVideoTrack: function (track) {
|
|
535
|
+
player.removeVideoTrack(track);
|
|
536
|
+
},
|
|
537
|
+
addVideoSource: function (remoteVideoTrack, track, onResize, muteHandler) {
|
|
538
|
+
this.currentTrack = track;
|
|
539
|
+
player.setVideoSource(remoteVideoTrack, onResize, muteHandler);
|
|
540
|
+
},
|
|
541
|
+
removeVideoSource: function (track) {
|
|
542
|
+
if (this.currentTrack && this.currentTrack.mid === track.mid) {
|
|
543
|
+
player.removeVideoSource();
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
showVideoTrack: function (track) {
|
|
547
|
+
player.showVideoTrack(track);
|
|
548
|
+
},
|
|
549
|
+
addAudioTrack: function (track, audioTrack, show) {
|
|
550
|
+
const stream = new MediaStream();
|
|
551
|
+
stream.addTrack(audioTrack);
|
|
552
|
+
const audioElement = document.createElement("audio");
|
|
553
|
+
if (!show) {
|
|
554
|
+
hideItem(audioElement);
|
|
555
|
+
}
|
|
556
|
+
audioElement.controls = "controls";
|
|
557
|
+
audioElement.muted = true;
|
|
558
|
+
audioElement.autoplay = true;
|
|
559
|
+
audioElement.onloadedmetadata = function (e) {
|
|
560
|
+
audioElement.play().then(function () {
|
|
561
|
+
if (Browser().isSafariWebRTC() && Browser().isiOS()) {
|
|
562
|
+
console.warn("Audio track should be manually unmuted in iOS Safari");
|
|
563
|
+
} else {
|
|
564
|
+
audioElement.muted = false;
|
|
243
565
|
}
|
|
244
|
-
|
|
245
|
-
|
|
566
|
+
});
|
|
567
|
+
};
|
|
568
|
+
audioElements.set(track.mid, audioElement);
|
|
569
|
+
audioDisplay.appendChild(audioElement);
|
|
570
|
+
audioElement.srcObject = stream;
|
|
571
|
+
},
|
|
572
|
+
removeAudioTrack: function (track) {
|
|
573
|
+
const audioElement = audioElements.get(track.mid);
|
|
574
|
+
if (audioElement) {
|
|
575
|
+
audioElement.remove();
|
|
576
|
+
audioElements.delete(track.mid);
|
|
246
577
|
}
|
|
578
|
+
},
|
|
579
|
+
setNickname: function (userId, nickname) {
|
|
580
|
+
const additionalUserId = userId ? "#" + getShortUserId(userId) : "";
|
|
581
|
+
participantNicknameDisplay.innerText = "Name: " + nickname + additionalUserId;
|
|
582
|
+
},
|
|
583
|
+
updateQuality: function (track, qualityName, available) {
|
|
584
|
+
player.updateQuality(qualityName, available);
|
|
585
|
+
},
|
|
586
|
+
addQuality: function (track, qualityName, available, onQualityPick) {
|
|
587
|
+
player.addQuality(qualityName, available, onQualityPick);
|
|
588
|
+
},
|
|
589
|
+
clearQualityState: function (track) {
|
|
590
|
+
player.clearQualityState();
|
|
591
|
+
},
|
|
592
|
+
pickQuality: function (track, qualityName) {
|
|
593
|
+
player.pickQuality(qualityName);
|
|
247
594
|
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const createVideoPlayer = function (participantDiv) {
|
|
599
|
+
|
|
600
|
+
const streamDisplay = createContainer(participantDiv);
|
|
601
|
+
|
|
602
|
+
const resolutionLabel = createInfoDisplay(streamDisplay, "0x0");
|
|
603
|
+
hideItem(resolutionLabel);
|
|
604
|
+
|
|
605
|
+
const trackNameDisplay = createInfoDisplay(streamDisplay, "track not set");
|
|
606
|
+
hideItem(trackNameDisplay);
|
|
607
|
+
|
|
608
|
+
const videoMuteDisplay = createContainer(streamDisplay);
|
|
609
|
+
|
|
610
|
+
const qualityDisplay = createContainer(streamDisplay);
|
|
611
|
+
|
|
612
|
+
const trackDisplay = createContainer(streamDisplay);
|
|
613
|
+
|
|
614
|
+
let videoElement;
|
|
615
|
+
|
|
616
|
+
const trackButtons = new Map();
|
|
617
|
+
const qualityButtons = new Map();
|
|
618
|
+
|
|
619
|
+
const lock = function () {
|
|
620
|
+
for (const btn of trackButtons.values()) {
|
|
621
|
+
btn.disabled = true;
|
|
253
622
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
623
|
+
for (const state of qualityButtons.values()) {
|
|
624
|
+
state.btn.disabled = true;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const unlock = function () {
|
|
629
|
+
for (const btn of trackButtons.values()) {
|
|
630
|
+
btn.disabled = false;
|
|
631
|
+
}
|
|
632
|
+
for (const state of qualityButtons.values()) {
|
|
633
|
+
state.btn.disabled = false;
|
|
263
634
|
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const setWebkitEventHandlers = function (video) {
|
|
638
|
+
let needRestart = false;
|
|
639
|
+
let isFullscreen = false;
|
|
640
|
+
// Use webkitbeginfullscreen event to detect full screen mode in iOS Safari
|
|
641
|
+
video.addEventListener("webkitbeginfullscreen", function () {
|
|
642
|
+
isFullscreen = true;
|
|
643
|
+
});
|
|
644
|
+
video.addEventListener("pause", function () {
|
|
645
|
+
if (needRestart) {
|
|
646
|
+
console.log("Media paused after fullscreen, continue...");
|
|
647
|
+
video.play();
|
|
648
|
+
needRestart = false;
|
|
649
|
+
} else {
|
|
650
|
+
console.log("Media paused by click, continue...");
|
|
651
|
+
video.play();
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
video.addEventListener("webkitendfullscreen", function () {
|
|
655
|
+
video.play();
|
|
656
|
+
needRestart = true;
|
|
657
|
+
isFullscreen = false;
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
const setEventHandlers = function (video) {
|
|
661
|
+
// Ignore play/pause button
|
|
662
|
+
video.addEventListener("pause", function () {
|
|
663
|
+
console.log("Media paused by click, continue...");
|
|
664
|
+
video.play();
|
|
665
|
+
});
|
|
666
|
+
}
|
|
264
667
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
668
|
+
const repickQuality = function (qualityName) {
|
|
669
|
+
for (const [quality, state] of qualityButtons.entries()) {
|
|
670
|
+
if (quality === qualityName) {
|
|
671
|
+
state.btn.style.color = QUALITY_COLORS.SELECTED;
|
|
672
|
+
} else if (state.btn.style.color === QUALITY_COLORS.SELECTED) {
|
|
673
|
+
if (state.available) {
|
|
674
|
+
state.btn.style.color = QUALITY_COLORS.AVAILABLE;
|
|
675
|
+
} else {
|
|
676
|
+
state.btn.style.color = QUALITY_COLORS.UNAVAILABLE;
|
|
272
677
|
}
|
|
273
678
|
}
|
|
274
679
|
}
|
|
275
|
-
}
|
|
680
|
+
}
|
|
276
681
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
682
|
+
return {
|
|
683
|
+
rootDiv: streamDisplay,
|
|
684
|
+
muteButton: null,
|
|
685
|
+
autoButton: null,
|
|
686
|
+
dispose: function () {
|
|
687
|
+
streamDisplay.remove();
|
|
688
|
+
},
|
|
689
|
+
clearQualityState: function () {
|
|
690
|
+
qualityButtons.forEach((state, qName) => {
|
|
691
|
+
state.btn.remove();
|
|
692
|
+
});
|
|
693
|
+
qualityButtons.clear();
|
|
694
|
+
},
|
|
695
|
+
addVideoTrack: function (track, asyncCallback) {
|
|
696
|
+
const trackButton = document.createElement("button");
|
|
697
|
+
trackButtons.set(track.mid, trackButton);
|
|
698
|
+
trackButton.innerText = "Track №" + track.mid + ": " + track.contentType;
|
|
699
|
+
trackButton.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
|
|
700
|
+
trackButton.style.color = QUALITY_COLORS.AVAILABLE;
|
|
701
|
+
const self = this;
|
|
702
|
+
trackButton.addEventListener('click', async function () {
|
|
703
|
+
console.log("Clicked on track button track.mid " + track.mid);
|
|
704
|
+
if (trackButton.style.color === QUALITY_COLORS.SELECTED) {
|
|
705
|
+
return
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
lock();
|
|
709
|
+
asyncCallback().then(() => {
|
|
710
|
+
self.showVideoTrack(track);
|
|
711
|
+
}).finally(() => {
|
|
712
|
+
unlock();
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
trackDisplay.appendChild(trackButton);
|
|
716
|
+
},
|
|
717
|
+
removeVideoTrack: function (track) {
|
|
718
|
+
const trackButton = trackButtons.get(track.mid);
|
|
719
|
+
if (trackButton) {
|
|
720
|
+
trackButton.remove();
|
|
721
|
+
trackButtons.delete(track.mid);
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
setVideoSource: function (remoteVideoTrack, onResize, onMute) {
|
|
725
|
+
if (!this.muteButton) {
|
|
726
|
+
const newVideoMuteBtn = document.createElement("button");
|
|
727
|
+
this.muteButton = newVideoMuteBtn;
|
|
728
|
+
newVideoMuteBtn.innerText = "mute";
|
|
729
|
+
newVideoMuteBtn.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
|
|
730
|
+
newVideoMuteBtn.addEventListener('click', async function () {
|
|
731
|
+
newVideoMuteBtn.disabled = true;
|
|
732
|
+
try {
|
|
733
|
+
if (newVideoMuteBtn.innerText === "mute") {
|
|
734
|
+
await onMute(true);
|
|
735
|
+
newVideoMuteBtn.innerText = "unmute";
|
|
736
|
+
} else if (newVideoMuteBtn.innerText === "unmute") {
|
|
737
|
+
await onMute(false);
|
|
738
|
+
newVideoMuteBtn.innerText = "mute";
|
|
739
|
+
}
|
|
740
|
+
} finally {
|
|
741
|
+
newVideoMuteBtn.disabled = false;
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
videoMuteDisplay.appendChild(newVideoMuteBtn);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (videoElement) {
|
|
748
|
+
videoElement.remove();
|
|
749
|
+
videoElement = null;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (!remoteVideoTrack) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
videoElement = document.createElement("video");
|
|
757
|
+
hideItem(videoElement);
|
|
758
|
+
videoElement.setAttribute("style", "display:none; border: solid; border-width: 1px");
|
|
759
|
+
|
|
760
|
+
const stream = new MediaStream();
|
|
761
|
+
|
|
762
|
+
streamDisplay.appendChild(videoElement);
|
|
763
|
+
videoElement.srcObject = stream;
|
|
764
|
+
videoElement.onloadedmetadata = function (e) {
|
|
765
|
+
videoElement.play();
|
|
766
|
+
};
|
|
767
|
+
videoElement.addEventListener("resize", function (event) {
|
|
768
|
+
showItem(resolutionLabel);
|
|
769
|
+
if (videoElement) {
|
|
770
|
+
resolutionLabel.innerText = videoElement.videoWidth + "x" + videoElement.videoHeight;
|
|
771
|
+
resizeVideo(event.target);
|
|
772
|
+
onResize();
|
|
349
773
|
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
774
|
+
});
|
|
775
|
+
stream.addTrack(remoteVideoTrack);
|
|
776
|
+
if (Browser().isSafariWebRTC()) {
|
|
777
|
+
videoElement.setAttribute("playsinline", "");
|
|
778
|
+
videoElement.setAttribute("webkit-playsinline", "");
|
|
779
|
+
setWebkitEventHandlers(videoElement);
|
|
780
|
+
} else {
|
|
781
|
+
setEventHandlers(videoElement);
|
|
782
|
+
}
|
|
783
|
+
},
|
|
784
|
+
removeVideoSource: function () {
|
|
785
|
+
if (videoElement) {
|
|
786
|
+
videoElement.remove();
|
|
787
|
+
videoElement = null;
|
|
788
|
+
}
|
|
789
|
+
if (this.muteButton) {
|
|
790
|
+
this.muteButton.remove();
|
|
791
|
+
this.muteButton = null;
|
|
792
|
+
}
|
|
793
|
+
hideItem(resolutionLabel);
|
|
794
|
+
trackNameDisplay.innerText = "track not set";
|
|
795
|
+
},
|
|
796
|
+
showVideoTrack: function (track) {
|
|
797
|
+
if (videoElement) {
|
|
798
|
+
showItem(videoElement);
|
|
799
|
+
}
|
|
800
|
+
for (const [mid, btn] of trackButtons.entries()) {
|
|
801
|
+
if (mid === track.mid) {
|
|
802
|
+
btn.style.color = QUALITY_COLORS.SELECTED;
|
|
803
|
+
} else if (btn.style.color === QUALITY_COLORS.SELECTED) {
|
|
804
|
+
btn.style.color = QUALITY_COLORS.AVAILABLE;
|
|
354
805
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
806
|
+
}
|
|
807
|
+
trackNameDisplay.innerText = "Current video track: " + track.mid;
|
|
808
|
+
showItem(trackNameDisplay);
|
|
809
|
+
},
|
|
810
|
+
updateQuality: function (qualityName, available) {
|
|
811
|
+
const value = qualityButtons.get(qualityName);
|
|
812
|
+
if (value) {
|
|
813
|
+
const qualityButton = value.btn;
|
|
814
|
+
value.available = available;
|
|
815
|
+
if (qualityButton.style.color === QUALITY_COLORS.SELECTED) {
|
|
358
816
|
return;
|
|
359
817
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
audio.controls = "controls";
|
|
363
|
-
audio.muted = true;
|
|
364
|
-
audio.autoplay = true;
|
|
365
|
-
if (Browser().isSafariWebRTC()) {
|
|
366
|
-
audio.setAttribute("playsinline", "");
|
|
367
|
-
audio.setAttribute("webkit-playsinline", "");
|
|
368
|
-
this.setWebkitEventHandlers(audio);
|
|
818
|
+
if (available) {
|
|
819
|
+
qualityButton.style.color = QUALITY_COLORS.AVAILABLE;
|
|
369
820
|
} else {
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
821
|
+
qualityButton.style.color = QUALITY_COLORS.UNAVAILABLE;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
addQuality: function (qualityName, available, onPickQuality) {
|
|
826
|
+
const qualityButton = document.createElement("button");
|
|
827
|
+
qualityButtons.set(qualityName, {btn: qualityButton, available: available});
|
|
828
|
+
qualityButton.innerText = qualityName;
|
|
829
|
+
qualityButton.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
|
|
830
|
+
if (available) {
|
|
831
|
+
qualityButton.style.color = QUALITY_COLORS.AVAILABLE;
|
|
832
|
+
} else {
|
|
833
|
+
qualityButton.style.color = QUALITY_COLORS.UNAVAILABLE;
|
|
834
|
+
}
|
|
835
|
+
qualityDisplay.appendChild(qualityButton);
|
|
836
|
+
qualityButton.addEventListener('click', async function () {
|
|
837
|
+
console.log("Clicked on quality button " + qualityName);
|
|
838
|
+
if (qualityButton.style.color === QUALITY_COLORS.SELECTED || qualityButton.style.color === QUALITY_COLORS.UNAVAILABLE || !videoElement) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
lock();
|
|
842
|
+
onPickQuality().finally(() => unlock());
|
|
843
|
+
});
|
|
844
|
+
},
|
|
845
|
+
pickQuality: function (qualityName) {
|
|
846
|
+
repickQuality(qualityName);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const createOneToOneParticipantView = function () {
|
|
852
|
+
|
|
853
|
+
const participantDiv = createContainer(null);
|
|
854
|
+
|
|
855
|
+
const audioDisplay = createContainer(participantDiv);
|
|
856
|
+
|
|
857
|
+
const participantNicknameDisplay = createInfoDisplay(participantDiv, "Name: ")
|
|
858
|
+
|
|
859
|
+
const videoPlayers = new Map();
|
|
860
|
+
const audioElements = new Map();
|
|
861
|
+
|
|
862
|
+
return {
|
|
863
|
+
rootDiv: participantDiv,
|
|
864
|
+
dispose: function () {
|
|
865
|
+
for (const player of videoPlayers.values()) {
|
|
866
|
+
player.dispose();
|
|
867
|
+
}
|
|
868
|
+
videoPlayers.clear();
|
|
869
|
+
for (const element of audioElements.values()) {
|
|
870
|
+
element.remove();
|
|
871
|
+
}
|
|
872
|
+
audioElements.clear();
|
|
873
|
+
},
|
|
874
|
+
addVideoTrack: function (track) {
|
|
875
|
+
const player = createVideoPlayer(participantDiv);
|
|
876
|
+
videoPlayers.set(track.mid, player);
|
|
877
|
+
},
|
|
878
|
+
removeVideoTrack: function (track) {
|
|
879
|
+
const player = videoPlayers.get(track.mid);
|
|
880
|
+
if (player) {
|
|
881
|
+
player.dispose();
|
|
882
|
+
}
|
|
883
|
+
},
|
|
884
|
+
addVideoSource: function (remoteVideoTrack, track, onResize, muteHandler) {
|
|
885
|
+
const player = videoPlayers.get(track.mid);
|
|
886
|
+
if (player) {
|
|
887
|
+
player.setVideoSource(remoteVideoTrack, onResize, muteHandler);
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
removeVideoSource: function (track) {
|
|
891
|
+
const player = videoPlayers.get(track.mid);
|
|
892
|
+
if (player) {
|
|
893
|
+
player.removeVideoSource();
|
|
894
|
+
}
|
|
895
|
+
},
|
|
896
|
+
showVideoTrack: function (track) {
|
|
897
|
+
const player = videoPlayers.get(track.mid);
|
|
898
|
+
if (player) {
|
|
899
|
+
player.showVideoTrack(track);
|
|
900
|
+
}
|
|
901
|
+
},
|
|
902
|
+
addAudioTrack: function (track, audioTrack, show) {
|
|
903
|
+
const stream = new MediaStream();
|
|
904
|
+
stream.addTrack(audioTrack);
|
|
905
|
+
const audioElement = document.createElement("audio");
|
|
906
|
+
if (!show) {
|
|
907
|
+
hideItem(audioElement);
|
|
908
|
+
}
|
|
909
|
+
audioElement.controls = "controls";
|
|
910
|
+
audioElement.muted = true;
|
|
911
|
+
audioElement.autoplay = true;
|
|
912
|
+
audioElement.onloadedmetadata = function (e) {
|
|
913
|
+
audioElement.play().then(function () {
|
|
914
|
+
if (Browser().isSafariWebRTC() && Browser().isiOS()) {
|
|
915
|
+
console.warn("Audio track should be manually unmuted in iOS Safari");
|
|
916
|
+
} else {
|
|
917
|
+
audioElement.muted = false;
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
};
|
|
921
|
+
audioElements.set(track.mid, audioElement);
|
|
922
|
+
audioDisplay.appendChild(audioElement);
|
|
923
|
+
audioElement.srcObject = stream;
|
|
924
|
+
},
|
|
925
|
+
removeAudioTrack: function (track) {
|
|
926
|
+
const audioElement = audioElements.get(track.mid);
|
|
927
|
+
if (audioElement) {
|
|
928
|
+
audioElement.remove();
|
|
929
|
+
audioElements.delete(track.mid);
|
|
930
|
+
}
|
|
931
|
+
},
|
|
932
|
+
setNickname: function (userId, nickname) {
|
|
933
|
+
const additionalUserId = userId ? "#" + getShortUserId(userId) : "";
|
|
934
|
+
participantNicknameDisplay.innerText = "Name: " + nickname + additionalUserId;
|
|
935
|
+
},
|
|
936
|
+
updateQuality: function (track, qualityName, available) {
|
|
937
|
+
const player = videoPlayers.get(track.mid);
|
|
938
|
+
if (player) {
|
|
939
|
+
player.updateQuality(qualityName, available);
|
|
940
|
+
}
|
|
941
|
+
},
|
|
942
|
+
addQuality: function (track, qualityName, available, onQualityPick) {
|
|
943
|
+
const player = videoPlayers.get(track.mid);
|
|
944
|
+
if (player) {
|
|
945
|
+
player.addQuality(qualityName, available, onQualityPick);
|
|
946
|
+
}
|
|
947
|
+
},
|
|
948
|
+
pickQuality: function (track, qualityName) {
|
|
949
|
+
const player = videoPlayers.get(track.mid);
|
|
950
|
+
if (player) {
|
|
951
|
+
player.pickQuality(qualityName);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
const createOneToOneParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
|
|
957
|
+
const instance = {
|
|
958
|
+
userId: userId,
|
|
959
|
+
nickname: nickname,
|
|
960
|
+
remoteVideoTracks: new Map(),
|
|
961
|
+
remoteAudioTracks: new Map(),
|
|
962
|
+
audioTracks: new Map(),
|
|
963
|
+
videoTracks: new Map(),
|
|
964
|
+
abrManagers: new Map(),
|
|
965
|
+
disposed: false,
|
|
966
|
+
dispose: async function () {
|
|
967
|
+
this.disposed = true;
|
|
968
|
+
participantView.dispose();
|
|
969
|
+
this.remoteVideoTracks.forEach((track, id) => {
|
|
970
|
+
track.dispose();
|
|
971
|
+
})
|
|
972
|
+
this.remoteVideoTracks.clear();
|
|
973
|
+
|
|
974
|
+
this.remoteAudioTracks.forEach((track, id) => {
|
|
975
|
+
track.dispose();
|
|
976
|
+
})
|
|
977
|
+
this.remoteAudioTracks.clear();
|
|
978
|
+
|
|
979
|
+
this.abrManagers.forEach((abrManager, id) => {
|
|
980
|
+
abrManager.stop();
|
|
981
|
+
})
|
|
982
|
+
this.abrManagers.clear();
|
|
983
|
+
|
|
984
|
+
},
|
|
985
|
+
addVideoTrack: function (track) {
|
|
986
|
+
this.videoTracks.set(track.mid, track);
|
|
987
|
+
if (!track.quality) {
|
|
988
|
+
track.quality = [];
|
|
989
|
+
}
|
|
990
|
+
participantView.addVideoTrack(track);
|
|
991
|
+
const self = this;
|
|
992
|
+
remoteTrackFactory.getVideoTrack().then((remoteTrack) => {
|
|
993
|
+
if (remoteTrack) {
|
|
994
|
+
if (self.disposed || !self.videoTracks.get(track.mid)) {
|
|
995
|
+
remoteTrack.dispose();
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
participantView.addVideoSource(remoteTrack.track, track, () => {
|
|
1000
|
+
const abrManager = self.abrManagers.get(track.id);
|
|
1001
|
+
if (!abrManager) {
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
if (abrManager.isAuto()) {
|
|
1005
|
+
abrManager.resume();
|
|
1006
|
+
}
|
|
1007
|
+
}, (mute) => {
|
|
1008
|
+
if (mute) {
|
|
1009
|
+
return self.muteVideo(track);
|
|
379
1010
|
} else {
|
|
380
|
-
|
|
381
|
-
audioStateButton.setButtonState();
|
|
1011
|
+
return self.unmuteVideo(track);
|
|
382
1012
|
}
|
|
383
1013
|
});
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
setVideo: function(stream) {
|
|
390
|
-
if (video) {
|
|
391
|
-
video.remove();
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (stream == null) {
|
|
395
|
-
video = null;
|
|
396
|
-
this.videoMid = undefined;
|
|
397
|
-
qualityDivs.forEach(function(div) {
|
|
398
|
-
div.remove();
|
|
1014
|
+
self.requestVideoTrack(track, remoteTrack).then(() => {
|
|
1015
|
+
participantView.showVideoTrack(track);
|
|
1016
|
+
}, (ex) => {
|
|
1017
|
+
participantView.removeVideoSource(track);
|
|
1018
|
+
remoteTrack.dispose();
|
|
399
1019
|
});
|
|
400
|
-
qualityDivs = [];
|
|
401
|
-
return;
|
|
402
1020
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
this.
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
for (let i = 0; i < trackInfo.quality.length; i++) {
|
|
434
|
-
abr.addQuality(trackInfo.quality[i]);
|
|
435
|
-
const qualityDiv = createQualityButton(trackInfo.quality[i], qualityDivs, qualitySwitchDisplay);
|
|
436
|
-
qualityDiv.addEventListener('click', function() {
|
|
437
|
-
console.log("Clicked on quality " + trackInfo.quality[i] + " trackId " + trackInfo.id);
|
|
438
|
-
if (qualityDiv.style.color === QUALITY_COLORS.UNAVAILABLE) {
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
setQualityButtonsColor(qualityDivs);
|
|
442
|
-
qualityDiv.style.color = QUALITY_COLORS.SELECTED;
|
|
443
|
-
abr.setManual();
|
|
444
|
-
abr.setQuality(trackInfo.quality[i]);
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
} else {
|
|
448
|
-
hideItem(qualitySwitchDisplay);
|
|
1021
|
+
}, (ex) => {
|
|
1022
|
+
console.log("Failed to get remote track " + ex);
|
|
1023
|
+
});
|
|
1024
|
+
},
|
|
1025
|
+
removeVideoTrack: function (track) {
|
|
1026
|
+
if (this.videoTracks.delete(track.mid)) {
|
|
1027
|
+
const remoteTrack = this.remoteVideoTracks.get(track.mid);
|
|
1028
|
+
if (remoteTrack) {
|
|
1029
|
+
this.remoteVideoTracks.delete(track.mid);
|
|
1030
|
+
remoteTrack.dispose();
|
|
1031
|
+
}
|
|
1032
|
+
participantView.removeVideoTrack(track);
|
|
1033
|
+
|
|
1034
|
+
const abrManager = this.abrManagers.get(track.id);
|
|
1035
|
+
if (abrManager) {
|
|
1036
|
+
this.abrManagers.delete(track.id);
|
|
1037
|
+
abrManager.clearQualityState();
|
|
1038
|
+
abrManager.stop();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
},
|
|
1042
|
+
addAudioTrack: function (track) {
|
|
1043
|
+
this.audioTracks.set(track.mid, track);
|
|
1044
|
+
const self = this;
|
|
1045
|
+
remoteTrackFactory.getAudioTrack().then((remoteTrack) => {
|
|
1046
|
+
if (remoteTrack) {
|
|
1047
|
+
if (self.disposed || !self.audioTracks.get(track.mid)) {
|
|
1048
|
+
remoteTrack.dispose();
|
|
1049
|
+
return;
|
|
449
1050
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
if (
|
|
453
|
-
|
|
454
|
-
|
|
1051
|
+
this.remoteAudioTracks.set(track.mid, remoteTrack);
|
|
1052
|
+
remoteTrack.demandTrack(track.id).then(() => {
|
|
1053
|
+
if (!self.audioTracks.get(track.mid)) {
|
|
1054
|
+
remoteTrack.dispose();
|
|
1055
|
+
self.remoteAudioTracks.delete(track.mid);
|
|
1056
|
+
return;
|
|
455
1057
|
}
|
|
456
|
-
|
|
457
|
-
|
|
1058
|
+
participantView.addAudioTrack(track, remoteTrack.track, displayOptions.showAudio);
|
|
1059
|
+
}, (ex) => {
|
|
1060
|
+
console.log("Failed demand track " + ex);
|
|
1061
|
+
remoteTrack.dispose();
|
|
1062
|
+
self.remoteAudioTracks.delete(track.mid);
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
}, (ex) => {
|
|
1066
|
+
console.log("Failed to get audio track " + ex);
|
|
1067
|
+
});
|
|
1068
|
+
},
|
|
1069
|
+
removeAudioTrack: function (track) {
|
|
1070
|
+
if (!this.audioTracks.delete(track.mid)) {
|
|
1071
|
+
return
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
participantView.removeAudioTrack(track);
|
|
1075
|
+
const remoteTrack = this.remoteAudioTracks.get(track.mid);
|
|
1076
|
+
if (remoteTrack) {
|
|
1077
|
+
this.remoteAudioTracks.delete(track.mid);
|
|
1078
|
+
remoteTrack.dispose();
|
|
1079
|
+
}
|
|
1080
|
+
},
|
|
1081
|
+
setUserId: function (userId) {
|
|
1082
|
+
this.userId = userId;
|
|
1083
|
+
},
|
|
1084
|
+
setNickname: function (nickname) {
|
|
1085
|
+
this.nickname = nickname;
|
|
1086
|
+
participantView.setNickname(this.userId ? this.userId : "", nickname);
|
|
1087
|
+
},
|
|
1088
|
+
updateQualityInfo: function (remoteTracks) {
|
|
1089
|
+
for (const remoteTrackQuality of remoteTracks) {
|
|
1090
|
+
const track = this.videoTracks.get(remoteTrackQuality.mid);
|
|
1091
|
+
if (!track) {
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
if (!this.remoteVideoTracks.get(track.mid)) {
|
|
1095
|
+
// update model and return, view not changed
|
|
1096
|
+
for (const remoteQualityInfo of remoteTrackQuality.quality) {
|
|
1097
|
+
const quality = track.quality.find((q) => q.quality === remoteQualityInfo.quality);
|
|
1098
|
+
if (quality) {
|
|
1099
|
+
quality.available = remoteQualityInfo.available;
|
|
1100
|
+
} else {
|
|
1101
|
+
track.quality.push(remoteQualityInfo);
|
|
458
1102
|
}
|
|
459
1103
|
}
|
|
1104
|
+
return;
|
|
460
1105
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
1106
|
+
let abrManager = this.abrManagers.get(track.id);
|
|
1107
|
+
if (abrManager && track.quality.length === 0 && remoteTrackQuality.quality.length > 0) {
|
|
1108
|
+
const self = this;
|
|
1109
|
+
participantView.addQuality(track, "Auto", true, async () => {
|
|
1110
|
+
const manager = self.abrManagers.get(track.id);
|
|
1111
|
+
if (!manager) {
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
manager.start();
|
|
1115
|
+
manager.setAuto();
|
|
1116
|
+
participantView.pickQuality(track, "Auto");
|
|
1117
|
+
})
|
|
1118
|
+
if (displayOptions.autoAbr) {
|
|
1119
|
+
abrManager.setAuto();
|
|
1120
|
+
abrManager.start();
|
|
1121
|
+
participantView.pickQuality(track, "Auto");
|
|
468
1122
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
1123
|
+
}
|
|
1124
|
+
for (const remoteQualityInfo of remoteTrackQuality.quality) {
|
|
1125
|
+
const localQuality = track.quality.find((q) => q.quality === remoteQualityInfo.quality);
|
|
1126
|
+
if (localQuality) {
|
|
1127
|
+
localQuality.available = remoteQualityInfo.available;
|
|
1128
|
+
if (abrManager) {
|
|
1129
|
+
abrManager.setQualityAvailable(remoteQualityInfo.quality, remoteQualityInfo.available);
|
|
1130
|
+
}
|
|
1131
|
+
if (displayOptions.quality) {
|
|
1132
|
+
participantView.updateQuality(track, localQuality.quality, localQuality.available);
|
|
1133
|
+
}
|
|
1134
|
+
} else {
|
|
1135
|
+
track.quality.push(remoteQualityInfo);
|
|
1136
|
+
if (abrManager) {
|
|
1137
|
+
abrManager.addQuality(remoteQualityInfo.quality);
|
|
1138
|
+
abrManager.setQualityAvailable(remoteQualityInfo.quality, remoteQualityInfo.available)
|
|
1139
|
+
}
|
|
1140
|
+
if (displayOptions.quality) {
|
|
1141
|
+
const self = this;
|
|
1142
|
+
participantView.addQuality(track, remoteQualityInfo.quality, remoteQualityInfo.available, async () => {
|
|
1143
|
+
const manager = self.abrManagers.get(track.id);
|
|
1144
|
+
if (manager) {
|
|
1145
|
+
manager.setManual();
|
|
1146
|
+
manager.setQuality(remoteQualityInfo.quality);
|
|
1147
|
+
}
|
|
1148
|
+
return self.pickQuality(track, remoteQualityInfo.quality);
|
|
1149
|
+
});
|
|
473
1150
|
}
|
|
474
1151
|
}
|
|
475
|
-
abr.setQualityAvailable(qualityInfo.quality, qualityInfo.available);
|
|
476
1152
|
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
},
|
|
1156
|
+
requestVideoTrack: async function (track, remoteTrack) {
|
|
1157
|
+
return new Promise((resolve, reject) => {
|
|
1158
|
+
if (!remoteTrack || !track) {
|
|
1159
|
+
reject(new Error("Remote and local track must be defined"));
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
const self = this;
|
|
1163
|
+
remoteTrack.demandTrack(track.id).then(() => {
|
|
1164
|
+
if (!self.videoTracks.get(track.mid)) {
|
|
1165
|
+
reject(new Error("Video track already removed from model"));
|
|
1166
|
+
return;
|
|
486
1167
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
1168
|
+
let abrManager = self.abrManagers.get(track.id);
|
|
1169
|
+
|
|
1170
|
+
if (abrManager) {
|
|
1171
|
+
abrManager.clearQualityState();
|
|
1172
|
+
} else if (abrFactory) {
|
|
1173
|
+
abrManager = abrFactory.createAbrManager();
|
|
1174
|
+
self.abrManagers.set(track.id, abrManager);
|
|
490
1175
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
1176
|
+
|
|
1177
|
+
if (abrManager) {
|
|
1178
|
+
abrManager.setTrack(remoteTrack);
|
|
1179
|
+
abrManager.stop();
|
|
1180
|
+
if (track.quality.length > 0) {
|
|
1181
|
+
participantView.addQuality(track, "Auto", true, async () => {
|
|
1182
|
+
const manager = self.abrManagers.get(track.id);
|
|
1183
|
+
if (!manager) {
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
manager.start();
|
|
1187
|
+
manager.setAuto();
|
|
1188
|
+
participantView.pickQuality(track, "Auto");
|
|
1189
|
+
});
|
|
1190
|
+
if (displayOptions.autoAbr) {
|
|
1191
|
+
abrManager.setAuto();
|
|
1192
|
+
abrManager.start();
|
|
1193
|
+
participantView.pickQuality(track, "Auto");
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
495
1196
|
}
|
|
1197
|
+
for (const qualityDescriptor of track.quality) {
|
|
1198
|
+
if (abrManager) {
|
|
1199
|
+
abrManager.addQuality(qualityDescriptor.quality);
|
|
1200
|
+
abrManager.setQualityAvailable(qualityDescriptor.quality, qualityDescriptor.available);
|
|
1201
|
+
}
|
|
1202
|
+
if (displayOptions.quality) {
|
|
1203
|
+
participantView.addQuality(track, qualityDescriptor.quality, qualityDescriptor.available, async () => {
|
|
1204
|
+
const manager = self.abrManagers.get(track.id);
|
|
1205
|
+
if (manager) {
|
|
1206
|
+
manager.setManual();
|
|
1207
|
+
manager.setQuality(qualityDescriptor.quality);
|
|
1208
|
+
}
|
|
1209
|
+
return self.pickQuality(track, qualityDescriptor.quality);
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
self.remoteVideoTracks.delete(track.mid);
|
|
1214
|
+
self.remoteVideoTracks.set(track.mid, remoteTrack);
|
|
1215
|
+
resolve();
|
|
1216
|
+
}, (ex) => {
|
|
1217
|
+
reject(ex);
|
|
496
1218
|
});
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
1219
|
+
});
|
|
1220
|
+
},
|
|
1221
|
+
pickQuality: async function (track, qualityName) {
|
|
1222
|
+
let remoteVideoTrack = this.remoteVideoTracks.get(track.mid);
|
|
1223
|
+
if (remoteVideoTrack) {
|
|
1224
|
+
return remoteVideoTrack.setPreferredQuality(qualityName).then(() => {
|
|
1225
|
+
participantView.pickQuality(track, qualityName);
|
|
503
1226
|
});
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
if (needRestart) {
|
|
514
|
-
console.log("Media paused after fullscreen, continue...");
|
|
515
|
-
video.play();
|
|
516
|
-
needRestart = false;
|
|
517
|
-
} else {
|
|
518
|
-
console.log("Media paused by click, continue...");
|
|
519
|
-
video.play();
|
|
520
|
-
}
|
|
1227
|
+
}
|
|
1228
|
+
},
|
|
1229
|
+
muteVideo: async function (track) {
|
|
1230
|
+
const remoteTrack = this.remoteVideoTracks.get(track.mid);
|
|
1231
|
+
if (remoteTrack) {
|
|
1232
|
+
return remoteTrack.mute();
|
|
1233
|
+
} else {
|
|
1234
|
+
return new Promise((resolve, reject) => {
|
|
1235
|
+
reject(new Error("Remote track not defined"));
|
|
521
1236
|
});
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
1237
|
+
}
|
|
1238
|
+
},
|
|
1239
|
+
unmuteVideo: async function (track) {
|
|
1240
|
+
const remoteTrack = this.remoteVideoTracks.get(track.mid);
|
|
1241
|
+
if (remoteTrack) {
|
|
1242
|
+
return remoteTrack.unmute();
|
|
1243
|
+
} else {
|
|
1244
|
+
return new Promise((resolve, reject) => {
|
|
1245
|
+
reject(new Error("Remote track not defined"));
|
|
526
1246
|
});
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
};
|
|
1250
|
+
instance.setUserId(userId);
|
|
1251
|
+
instance.setNickname(nickname);
|
|
1252
|
+
return instance;
|
|
1253
|
+
}
|
|
535
1254
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
1255
|
+
const createOneToManyParticipantModel = function (userId, nickname, participantView, remoteTrackFactory, abrFactory, displayOptions) {
|
|
1256
|
+
// reject may received before track removed from model.
|
|
1257
|
+
// If a new rejection reason is added in addition to track deletion,
|
|
1258
|
+
// a rejection reason analysis must be added
|
|
1259
|
+
const repickTrack = function (model, failedTrack) {
|
|
1260
|
+
if (!model.remoteVideoTrack) {
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
const tracks = new Map(model.videoTracks);
|
|
1264
|
+
if (failedTrack) {
|
|
1265
|
+
tracks.delete(failedTrack.mid);
|
|
1266
|
+
participantView.removeVideoSource(failedTrack);
|
|
542
1267
|
}
|
|
543
|
-
}
|
|
544
1268
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (
|
|
552
|
-
|
|
553
|
-
break;
|
|
1269
|
+
if (tracks.size > 0) {
|
|
1270
|
+
const anotherTrack = tracks.values().next().value;
|
|
1271
|
+
participantView.addVideoSource(model.remoteVideoTrack.track, anotherTrack, () => {
|
|
1272
|
+
if (!model.abr) {
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
if (model.abr.isAuto()) {
|
|
1276
|
+
model.abr.resume();
|
|
554
1277
|
}
|
|
1278
|
+
}, (mute) => {
|
|
1279
|
+
if (mute) {
|
|
1280
|
+
return model.muteVideo(anotherTrack);
|
|
1281
|
+
} else {
|
|
1282
|
+
return model.unmuteVideo(anotherTrack);
|
|
1283
|
+
}
|
|
1284
|
+
});
|
|
1285
|
+
model.requestVideoTrack(anotherTrack, model.remoteVideoTrack).then(() => {
|
|
1286
|
+
participantView.showVideoTrack(anotherTrack)
|
|
1287
|
+
}, (ex) => {
|
|
1288
|
+
console.log("Failed to request track " + anotherTrack.mid + " " + ex);
|
|
1289
|
+
repickTrack(model, anotherTrack);
|
|
1290
|
+
});
|
|
1291
|
+
} else {
|
|
1292
|
+
if (model.abr) {
|
|
1293
|
+
model.abr.stop();
|
|
555
1294
|
}
|
|
556
|
-
|
|
557
|
-
|
|
1295
|
+
participantView.clearQualityState();
|
|
1296
|
+
if (model.remoteVideoTrack) {
|
|
1297
|
+
model.remoteVideoTrack.dispose();
|
|
1298
|
+
model.remoteVideoTrack = null;
|
|
1299
|
+
model.videoEnabled = false;
|
|
558
1300
|
}
|
|
559
1301
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
} else {
|
|
580
|
-
console.warn("Failed to find participant for track " + transceiver.receiver.track.id);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const AudioStateButton = function() {
|
|
585
|
-
let button = {
|
|
586
|
-
audio: null,
|
|
587
|
-
contentType: "",
|
|
588
|
-
displayButton: null,
|
|
589
|
-
makeButton: function(parent, audio) {
|
|
590
|
-
button.setAudio(audio);
|
|
591
|
-
button.displayButton = document.createElement("button");
|
|
592
|
-
button.displayButton.innerHTML = button.audioState();
|
|
593
|
-
button.displayButton.addEventListener("click", function() {
|
|
594
|
-
button.audio.muted = !button.audio.muted;
|
|
595
|
-
button.displayButton.innerHTML = button.audioState();
|
|
596
|
-
});
|
|
597
|
-
parent.appendChild(button.displayButton);
|
|
598
|
-
|
|
599
|
-
},
|
|
600
|
-
setAudio: function(audio) {
|
|
601
|
-
button.audio = audio;
|
|
602
|
-
},
|
|
603
|
-
setButtonState: function() {
|
|
604
|
-
if (button.displayButton) {
|
|
605
|
-
button.displayButton.innerHTML = button.audioState();
|
|
606
|
-
}
|
|
607
|
-
},
|
|
608
|
-
setContentType: function(type) {
|
|
609
|
-
button.contentType = type;
|
|
610
|
-
button.setButtonState();
|
|
611
|
-
},
|
|
612
|
-
audioState: function() {
|
|
613
|
-
let state = "";
|
|
614
|
-
if (button.audio) {
|
|
615
|
-
if (button.audio.muted) {
|
|
616
|
-
state = "Unmute";
|
|
617
|
-
} else {
|
|
618
|
-
state = "Mute";
|
|
619
|
-
}
|
|
620
|
-
if (button.contentType) {
|
|
621
|
-
state = state + " " + button.contentType;
|
|
622
|
-
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
const requestTrackAndPick = function (model, targetTrack) {
|
|
1305
|
+
if (model.videoTracks.size === 0) {
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
if (!targetTrack) {
|
|
1309
|
+
targetTrack = model.videoTracks.values().next().value
|
|
1310
|
+
}
|
|
1311
|
+
if (!model.videoTracks.get(targetTrack.mid)) {
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
if (!model.videoEnabled) {
|
|
1315
|
+
model.videoEnabled = true;
|
|
1316
|
+
remoteTrackFactory.getVideoTrack().then((remoteTrack) => {
|
|
1317
|
+
if (!remoteTrack) {
|
|
1318
|
+
model.videoEnabled = false;
|
|
1319
|
+
return;
|
|
623
1320
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
const ABR = function(interval, thresholds, keepGoodTimeout, tryUpperTimeout) {
|
|
631
|
-
let abr = {
|
|
632
|
-
track: null,
|
|
633
|
-
interval: interval,
|
|
634
|
-
thresholds: thresholds,
|
|
635
|
-
qualities: [],
|
|
636
|
-
currentQualityName: null,
|
|
637
|
-
statTimer: null,
|
|
638
|
-
paused: false,
|
|
639
|
-
manual: false,
|
|
640
|
-
keepGoodTimeout: keepGoodTimeout,
|
|
641
|
-
keepGoodTimer: null,
|
|
642
|
-
tryUpperTimeout: tryUpperTimeout,
|
|
643
|
-
tryUpperTimer: null,
|
|
644
|
-
start: function() {
|
|
645
|
-
if (abr.interval) {
|
|
646
|
-
const thresholds = Thresholds();
|
|
647
|
-
for (const threshold of abr.thresholds) {
|
|
648
|
-
thresholds.add(threshold.parameter, threshold.maxLeap);
|
|
649
|
-
}
|
|
650
|
-
abr.statsTimer = setInterval(() => {
|
|
651
|
-
if (abr.track) {
|
|
652
|
-
room.getStats(abr.track, constants.SFU_RTC_STATS_TYPE.INBOUND, (stats) => {
|
|
653
|
-
if (thresholds.isReached(stats)) {
|
|
654
|
-
abr.shiftDown();
|
|
655
|
-
} else {
|
|
656
|
-
abr.useGoodQuality();
|
|
657
|
-
}
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
}, abr.interval);
|
|
661
|
-
}
|
|
662
|
-
},
|
|
663
|
-
stop: function() {
|
|
664
|
-
abr.stopKeeping();
|
|
665
|
-
abr.stopTrying();
|
|
666
|
-
if (abr.statsTimer) {
|
|
667
|
-
clearInterval(abr.statsTimer);
|
|
668
|
-
abr.statsTimer = null;
|
|
669
|
-
}
|
|
670
|
-
},
|
|
671
|
-
isEnabled: function () {
|
|
672
|
-
return (abr.interval > 0);
|
|
673
|
-
},
|
|
674
|
-
pause: function() {
|
|
675
|
-
abr.paused = true;
|
|
676
|
-
},
|
|
677
|
-
resume: function() {
|
|
678
|
-
abr.paused = false;
|
|
679
|
-
},
|
|
680
|
-
setAuto: function() {
|
|
681
|
-
abr.manual = false;
|
|
682
|
-
abr.resume();
|
|
683
|
-
},
|
|
684
|
-
setManual: function() {
|
|
685
|
-
abr.manual = true;
|
|
686
|
-
abr.pause();
|
|
687
|
-
},
|
|
688
|
-
isAuto: function() {
|
|
689
|
-
return !abr.manual;
|
|
690
|
-
},
|
|
691
|
-
setTrack: function(track) {
|
|
692
|
-
abr.track = track;
|
|
693
|
-
},
|
|
694
|
-
setQualitiesList: function(qualities) {
|
|
695
|
-
abr.qualities = qualities;
|
|
696
|
-
},
|
|
697
|
-
addQuality: function(name) {
|
|
698
|
-
abr.qualities.push({name: name, available: false, good: true});
|
|
699
|
-
},
|
|
700
|
-
setQualityAvailable: function(name, available) {
|
|
701
|
-
for (let i = 0; i < abr.qualities.length; i++) {
|
|
702
|
-
if (name === abr.qualities[i].name) {
|
|
703
|
-
abr.qualities[i].available = available;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
},
|
|
707
|
-
setQualityGood: function(name, good) {
|
|
708
|
-
if (name) {
|
|
709
|
-
for (let i = 0; i < abr.qualities.length; i++) {
|
|
710
|
-
if (name === abr.qualities[i].name) {
|
|
711
|
-
abr.qualities[i].good = good;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
1321
|
+
if (model.disposed || model.videoTracks.size === 0) {
|
|
1322
|
+
remoteTrack.dispose();
|
|
1323
|
+
model.videoEnabled = false;
|
|
1324
|
+
return;
|
|
714
1325
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
}
|
|
1326
|
+
model.remoteVideoTrack = remoteTrack;
|
|
1327
|
+
if (!model.videoTracks.get(targetTrack.mid)) {
|
|
1328
|
+
repickTrack(model, targetTrack);
|
|
1329
|
+
} else {
|
|
1330
|
+
repickTrack(model, null);
|
|
721
1331
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1332
|
+
}, (ex) => {
|
|
1333
|
+
model.videoEnabled = false;
|
|
1334
|
+
console.log("Failed to get remote track " + ex);
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
const instance = {
|
|
1339
|
+
userId: userId,
|
|
1340
|
+
nickname: nickname,
|
|
1341
|
+
videoEnabled: false,
|
|
1342
|
+
currentTrack: null,
|
|
1343
|
+
remoteVideoTrack: null,
|
|
1344
|
+
remoteAudioTracks: new Map(),
|
|
1345
|
+
audioTracks: new Map(),
|
|
1346
|
+
videoTracks: new Map(),
|
|
1347
|
+
abr: null,
|
|
1348
|
+
disposed: false,
|
|
1349
|
+
dispose: async function () {
|
|
1350
|
+
this.disposed = true;
|
|
1351
|
+
participantView.dispose();
|
|
1352
|
+
if (this.remoteVideoTrack) {
|
|
1353
|
+
const remoteTrack = this.remoteVideoTrack;
|
|
1354
|
+
this.remoteVideoTrack = null;
|
|
1355
|
+
remoteTrack.dispose();
|
|
1356
|
+
}
|
|
1357
|
+
this.remoteAudioTracks.forEach((track, id) => {
|
|
1358
|
+
track.dispose();
|
|
1359
|
+
})
|
|
1360
|
+
if (this.abr) {
|
|
1361
|
+
this.abr.stop();
|
|
1362
|
+
}
|
|
1363
|
+
this.remoteAudioTracks.clear();
|
|
1364
|
+
},
|
|
1365
|
+
addVideoTrack: function (track) {
|
|
1366
|
+
this.videoTracks.set(track.mid, track);
|
|
1367
|
+
if (!track.quality) {
|
|
1368
|
+
track.quality = [];
|
|
1369
|
+
}
|
|
1370
|
+
const self = this;
|
|
1371
|
+
participantView.addVideoTrack(track, () => {
|
|
1372
|
+
if (self.disposed) {
|
|
1373
|
+
return new Promise((resolve, reject) => {
|
|
1374
|
+
reject(new Error("Model disposed"));
|
|
1375
|
+
});
|
|
729
1376
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1377
|
+
|
|
1378
|
+
if (self.remoteVideoTrack) {
|
|
1379
|
+
return new Promise((resolve, reject) => {
|
|
1380
|
+
self.requestVideoTrack(track, self.remoteVideoTrack).then(() => {
|
|
1381
|
+
resolve();
|
|
1382
|
+
}, (ex) => {
|
|
1383
|
+
reject(ex);
|
|
1384
|
+
});
|
|
1385
|
+
});
|
|
1386
|
+
} else {
|
|
1387
|
+
return new Promise((resolve, reject) => {
|
|
1388
|
+
reject(new Error("Remote track is null"));
|
|
1389
|
+
requestTrackAndPick(self, track);
|
|
1390
|
+
});
|
|
735
1391
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1392
|
+
});
|
|
1393
|
+
requestTrackAndPick(this, track);
|
|
1394
|
+
},
|
|
1395
|
+
removeVideoTrack: function (track) {
|
|
1396
|
+
this.videoTracks.delete(track.mid);
|
|
1397
|
+
participantView.removeVideoTrack(track);
|
|
1398
|
+
if (this.currentTrack && this.currentTrack.mid === track.mid) {
|
|
1399
|
+
repickTrack(this, track);
|
|
1400
|
+
}
|
|
1401
|
+
},
|
|
1402
|
+
addAudioTrack: async function (track) {
|
|
1403
|
+
this.audioTracks.set(track.mid, track);
|
|
1404
|
+
const self = this;
|
|
1405
|
+
remoteTrackFactory.getAudioTrack().then((remoteTrack) => {
|
|
1406
|
+
if (!remoteTrack) {
|
|
1407
|
+
return;
|
|
743
1408
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
quality = abr.qualities[i];
|
|
748
|
-
break;
|
|
749
|
-
}
|
|
1409
|
+
if (self.disposed || !self.audioTracks.get(track.mid)) {
|
|
1410
|
+
remoteTrack.dispose();
|
|
1411
|
+
return;
|
|
750
1412
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
let quality = abr.getLowerQuality(abr.currentQualityName);
|
|
758
|
-
if (quality) {
|
|
759
|
-
console.log("Switching down to " + quality.name + " quality");
|
|
760
|
-
abr.setQuality(quality.name);
|
|
1413
|
+
this.remoteAudioTracks.set(track.mid, remoteTrack);
|
|
1414
|
+
remoteTrack.demandTrack(track.id).then(() => {
|
|
1415
|
+
if (!self.audioTracks.get(track.mid)) {
|
|
1416
|
+
remoteTrack.dispose();
|
|
1417
|
+
self.remoteAudioTracks.delete(track.mid);
|
|
1418
|
+
return;
|
|
761
1419
|
}
|
|
1420
|
+
participantView.addAudioTrack(track, remoteTrack.track, displayOptions.showAudio);
|
|
1421
|
+
}, (ex) => {
|
|
1422
|
+
console.log("Failed demand track " + ex);
|
|
1423
|
+
remoteTrack.dispose();
|
|
1424
|
+
self.remoteAudioTracks.delete(track.mid);
|
|
1425
|
+
});
|
|
1426
|
+
}, (ex) => {
|
|
1427
|
+
console.log("Failed to get audio track " + ex);
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
},
|
|
1431
|
+
removeAudioTrack: function (track) {
|
|
1432
|
+
if (!this.audioTracks.delete(track.mid)) {
|
|
1433
|
+
return
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
participantView.removeAudioTrack(track);
|
|
1437
|
+
const remoteTrack = this.remoteAudioTracks.get(track.mid);
|
|
1438
|
+
if (remoteTrack) {
|
|
1439
|
+
this.remoteAudioTracks.delete(track.mid);
|
|
1440
|
+
remoteTrack.dispose();
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
},
|
|
1444
|
+
setUserId: function (userId) {
|
|
1445
|
+
this.userId = userId;
|
|
1446
|
+
},
|
|
1447
|
+
setNickname: function (nickname) {
|
|
1448
|
+
this.nickname = nickname;
|
|
1449
|
+
participantView.setNickname(this.userId ? this.userId : "", nickname);
|
|
1450
|
+
},
|
|
1451
|
+
updateQualityInfo: function (remoteTracks) {
|
|
1452
|
+
for (const remoteTrackQuality of remoteTracks) {
|
|
1453
|
+
const track = this.videoTracks.get(remoteTrackQuality.mid);
|
|
1454
|
+
if (!track) {
|
|
1455
|
+
continue;
|
|
762
1456
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
console.log("Switching up to " + quality.name + " quality");
|
|
770
|
-
abr.setQuality(quality.name);
|
|
1457
|
+
if (!this.currentTrack || this.currentTrack.mid !== track.mid) {
|
|
1458
|
+
// update model and return, view not changed
|
|
1459
|
+
for (const remoteQualityInfo of remoteTrackQuality.quality) {
|
|
1460
|
+
const quality = track.quality.find((q) => q.quality === remoteQualityInfo.quality);
|
|
1461
|
+
if (quality) {
|
|
1462
|
+
quality.available = remoteQualityInfo.available;
|
|
771
1463
|
} else {
|
|
772
|
-
|
|
1464
|
+
track.quality.push(remoteQualityInfo);
|
|
773
1465
|
}
|
|
774
1466
|
}
|
|
1467
|
+
return;
|
|
775
1468
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1469
|
+
if (this.abr && track.quality.length === 0 && remoteTrackQuality.quality.length > 0) {
|
|
1470
|
+
const self = this;
|
|
1471
|
+
participantView.addQuality(track, "Auto", true, async () => {
|
|
1472
|
+
if (!self.abr) {
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
self.abr.start();
|
|
1476
|
+
self.abr.setAuto();
|
|
1477
|
+
participantView.pickQuality(track, "Auto");
|
|
1478
|
+
})
|
|
1479
|
+
if (displayOptions.autoAbr && this.abr) {
|
|
1480
|
+
this.abr.setAuto();
|
|
1481
|
+
this.abr.start();
|
|
1482
|
+
participantView.pickQuality(track, "Auto");
|
|
782
1483
|
}
|
|
783
|
-
abr.setQualityGood(abr.currentQualityName, true);
|
|
784
|
-
abr.keepGoodQuality();
|
|
785
|
-
}
|
|
786
|
-
},
|
|
787
|
-
keepGoodQuality: function() {
|
|
788
|
-
if (abr.keepGoodTimeout && !abr.keepGoodTimer && abr.getUpperQuality(abr.currentQualityName)) {
|
|
789
|
-
abr.keepGoodTimer = setTimeout(() => {
|
|
790
|
-
abr.shiftUp();
|
|
791
|
-
abr.stopKeeping();
|
|
792
|
-
}, abr.keepGoodTimeout);
|
|
793
|
-
}
|
|
794
|
-
},
|
|
795
|
-
stopKeeping: function() {
|
|
796
|
-
if (abr.keepGoodTimer) {
|
|
797
|
-
clearTimeout(abr.keepGoodTimer);
|
|
798
|
-
abr.keepGoodTimer = null;
|
|
799
1484
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1485
|
+
for (const remoteQualityInfo of remoteTrackQuality.quality) {
|
|
1486
|
+
const localQuality = track.quality.find((q) => q.quality === remoteQualityInfo.quality);
|
|
1487
|
+
if (localQuality) {
|
|
1488
|
+
localQuality.available = remoteQualityInfo.available;
|
|
1489
|
+
if (this.abr) {
|
|
1490
|
+
this.abr.setQualityAvailable(remoteQualityInfo.quality, remoteQualityInfo.available)
|
|
1491
|
+
}
|
|
1492
|
+
if (displayOptions.quality) {
|
|
1493
|
+
participantView.updateQuality(track, localQuality.quality, localQuality.available);
|
|
1494
|
+
}
|
|
1495
|
+
} else {
|
|
1496
|
+
track.quality.push(remoteQualityInfo);
|
|
1497
|
+
if (this.abr) {
|
|
1498
|
+
this.abr.addQuality(remoteQualityInfo.quality);
|
|
1499
|
+
this.abr.setQualityAvailable(remoteQualityInfo.quality, remoteQualityInfo.available)
|
|
1500
|
+
}
|
|
1501
|
+
if (displayOptions.quality) {
|
|
1502
|
+
const self = this;
|
|
1503
|
+
participantView.addQuality(track, remoteQualityInfo.quality, remoteQualityInfo.available, async () => {
|
|
1504
|
+
if (self.abr) {
|
|
1505
|
+
self.abr.setManual();
|
|
1506
|
+
self.abr.setQuality(remoteQualityInfo.quality);
|
|
1507
|
+
}
|
|
1508
|
+
return self.pickQuality(track, remoteQualityInfo.quality);
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
808
1512
|
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1513
|
+
}
|
|
1514
|
+
},
|
|
1515
|
+
requestVideoTrack: async function (track, remoteTrack) {
|
|
1516
|
+
return new Promise((resolve, reject) => {
|
|
1517
|
+
if (!remoteTrack || !track) {
|
|
1518
|
+
reject(new Error("Remote and local track must be defined"));
|
|
1519
|
+
return;
|
|
814
1520
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1521
|
+
const self = this;
|
|
1522
|
+
remoteTrack.demandTrack(track.id).then(() => {
|
|
1523
|
+
// channels reordering case, must be removed after channels unification
|
|
1524
|
+
if (!self.videoTracks.get(track.mid)) {
|
|
1525
|
+
reject(new Error("Video track already removed from model"));
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
self.currentTrack = track;
|
|
1529
|
+
participantView.clearQualityState(track);
|
|
1530
|
+
if (self.abr) {
|
|
1531
|
+
self.abr.stop();
|
|
1532
|
+
self.abr.clearQualityState();
|
|
1533
|
+
self.abr.setTrack(remoteTrack);
|
|
1534
|
+
|
|
1535
|
+
if (track.quality.length > 0) {
|
|
1536
|
+
participantView.addQuality(track, "Auto", true, async () => {
|
|
1537
|
+
if (!self.abr) {
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
self.abr.start();
|
|
1541
|
+
self.abr.setAuto();
|
|
1542
|
+
participantView.pickQuality(track, "Auto");
|
|
1543
|
+
})
|
|
1544
|
+
}
|
|
1545
|
+
if (displayOptions.autoAbr) {
|
|
1546
|
+
self.abr.setAuto();
|
|
1547
|
+
self.abr.start();
|
|
1548
|
+
participantView.pickQuality(track, "Auto");
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
for (const qualityDescriptor of track.quality) {
|
|
1552
|
+
if (self.abr) {
|
|
1553
|
+
self.abr.addQuality(qualityDescriptor.quality);
|
|
1554
|
+
self.abr.setQualityAvailable(qualityDescriptor.quality, qualityDescriptor.available);
|
|
1555
|
+
}
|
|
1556
|
+
if (displayOptions.quality) {
|
|
1557
|
+
participantView.addQuality(track, qualityDescriptor.quality, qualityDescriptor.available, async () => {
|
|
1558
|
+
if (self.abr) {
|
|
1559
|
+
self.abr.setManual();
|
|
1560
|
+
self.abr.setQuality(qualityDescriptor.quality);
|
|
1561
|
+
}
|
|
1562
|
+
return self.pickQuality(track, qualityDescriptor.quality);
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
resolve();
|
|
1567
|
+
}, (ex) => reject(ex));
|
|
1568
|
+
});
|
|
1569
|
+
},
|
|
1570
|
+
pickQuality: async function (track, qualityName) {
|
|
1571
|
+
if (this.remoteVideoTrack) {
|
|
1572
|
+
return this.remoteVideoTrack.setPreferredQuality(qualityName).then(() => participantView.pickQuality(track, qualityName));
|
|
1573
|
+
}
|
|
1574
|
+
},
|
|
1575
|
+
muteVideo: async function (track) {
|
|
1576
|
+
if (this.remoteVideoTrack) {
|
|
1577
|
+
return this.remoteVideoTrack.mute();
|
|
1578
|
+
} else {
|
|
1579
|
+
return new Promise((resolve, reject) => {
|
|
1580
|
+
reject(new Error("Remote track not defined"));
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
},
|
|
1584
|
+
unmuteVideo: async function (track) {
|
|
1585
|
+
if (this.remoteVideoTrack) {
|
|
1586
|
+
return this.remoteVideoTrack.unmute();
|
|
1587
|
+
} else {
|
|
1588
|
+
return new Promise((resolve, reject) => {
|
|
1589
|
+
reject(new Error("Remote track not defined"));
|
|
1590
|
+
});
|
|
821
1591
|
}
|
|
822
1592
|
}
|
|
823
|
-
|
|
1593
|
+
};
|
|
1594
|
+
instance.setUserId(userId);
|
|
1595
|
+
instance.setNickname(nickname);
|
|
1596
|
+
if (abrFactory) {
|
|
1597
|
+
instance.abr = abrFactory.createAbrManager();
|
|
824
1598
|
}
|
|
1599
|
+
return instance;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
|
|
1603
|
+
const createDefaultMeetingController = function (room, meetingModel) {
|
|
1604
|
+
const constants = SFU.constants;
|
|
1605
|
+
room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, async function (e) {
|
|
1606
|
+
for (const idName of e.participants) {
|
|
1607
|
+
meetingModel.addParticipant(idName.userId, idName.name);
|
|
1608
|
+
}
|
|
1609
|
+
}).on(constants.SFU_ROOM_EVENT.JOINED, async function (e) {
|
|
1610
|
+
meetingModel.addParticipant(e.userId, e.name);
|
|
1611
|
+
}).on(constants.SFU_ROOM_EVENT.LEFT, function (e) {
|
|
1612
|
+
meetingModel.removeParticipant(e.userId);
|
|
1613
|
+
}).on(constants.SFU_ROOM_EVENT.ADD_TRACKS, async function (e) {
|
|
1614
|
+
meetingModel.addTracks(e.info.userId, e.info.info);
|
|
1615
|
+
}).on(constants.SFU_ROOM_EVENT.REMOVE_TRACKS, async function (e) {
|
|
1616
|
+
meetingModel.removeTracks(e.info.userId, e.info.info);
|
|
1617
|
+
}).on(constants.SFU_ROOM_EVENT.TRACK_QUALITY_STATE, async function (e) {
|
|
1618
|
+
meetingModel.updateQualityInfo(e.info.userId, e.info.tracks);
|
|
1619
|
+
}).on(constants.SFU_ROOM_EVENT.ENDED, function (e) {
|
|
1620
|
+
meetingModel.end();
|
|
1621
|
+
});
|
|
1622
|
+
meetingModel.setMeetingName(room.id());
|
|
1623
|
+
|
|
1624
|
+
|
|
1625
|
+
const stop = function () {
|
|
1626
|
+
meetingModel.end();
|
|
1627
|
+
};
|
|
825
1628
|
|
|
826
1629
|
return {
|
|
827
1630
|
stop: stop
|
|
828
1631
|
}
|
|
829
1632
|
}
|
|
830
1633
|
|
|
831
|
-
const
|
|
1634
|
+
const remoteTrackProvider = function (room) {
|
|
1635
|
+
return {
|
|
1636
|
+
getVideoTrack: async function () {
|
|
1637
|
+
return await room.getRemoteTrack("VIDEO", false);
|
|
1638
|
+
},
|
|
1639
|
+
getAudioTrack: async function () {
|
|
1640
|
+
return await room.getRemoteTrack("AUDIO", true);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
const initDefaultRemoteDisplay = function (room, div, displayOptions, abrOptions) {
|
|
1645
|
+
const participantFactory = createParticipantFactory(remoteTrackProvider(room), createOneToManyParticipantView, createOneToManyParticipantModel);
|
|
1646
|
+
return initRemoteDisplay(room, div, displayOptions, abrOptions, createDefaultMeetingController, createDefaultMeetingModel, createDefaultMeetingView, participantFactory)
|
|
1647
|
+
}
|
|
1648
|
+
/*
|
|
1649
|
+
display options:
|
|
1650
|
+
autoAbr - choose abr by default
|
|
1651
|
+
quality - show quality buttons
|
|
1652
|
+
showAudio - show audio elements
|
|
1653
|
+
*/
|
|
1654
|
+
const initRemoteDisplay = function (room, div, displayOptions, abrOptions, meetingController, meetingModel, meetingView, participantFactory) {
|
|
1655
|
+
// Validate options first
|
|
1656
|
+
if (!div) {
|
|
1657
|
+
throw new Error("Main div to place all the media tag is not defined");
|
|
1658
|
+
}
|
|
1659
|
+
if (!room) {
|
|
1660
|
+
throw new Error("Room is not defined");
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
const dOptions = displayOptions || {quality: true, type: true, showAudio: false};
|
|
1664
|
+
let abrFactory;
|
|
1665
|
+
if (abrOptions) {
|
|
1666
|
+
abrFactory = abrManagerFactory(room, abrOptions);
|
|
1667
|
+
}
|
|
1668
|
+
participantFactory.abrFactory = abrFactory;
|
|
1669
|
+
participantFactory.displayOptions = dOptions;
|
|
1670
|
+
return meetingController(room, meetingModel(meetingView(div), participantFactory));
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
const resizeVideo = function (video, width, height) {
|
|
1674
|
+
// TODO: fix
|
|
1675
|
+
if (video) {
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
832
1678
|
if (!video.parentNode) {
|
|
833
1679
|
return;
|
|
834
1680
|
}
|
|
@@ -836,12 +1682,12 @@ const resizeVideo = function(video, width, height) {
|
|
|
836
1682
|
video.videoWidth = video.width;
|
|
837
1683
|
video.videoHeight = video.height;
|
|
838
1684
|
}
|
|
839
|
-
|
|
840
|
-
|
|
1685
|
+
const display = video.parentNode;
|
|
1686
|
+
const parentSize = {
|
|
841
1687
|
w: display.parentNode.clientWidth,
|
|
842
1688
|
h: display.parentNode.clientHeight
|
|
843
1689
|
};
|
|
844
|
-
|
|
1690
|
+
let newSize;
|
|
845
1691
|
if (width && height) {
|
|
846
1692
|
newSize = downScaleToFitSize(width, height, parentSize.w, parentSize.h);
|
|
847
1693
|
} else {
|
|
@@ -851,7 +1697,7 @@ const resizeVideo = function(video, width, height) {
|
|
|
851
1697
|
display.style.height = newSize.h + "px";
|
|
852
1698
|
|
|
853
1699
|
//vertical align
|
|
854
|
-
|
|
1700
|
+
let margin = 0;
|
|
855
1701
|
if (parentSize.h - newSize.h > 1) {
|
|
856
1702
|
margin = Math.floor((parentSize.h - newSize.h) / 2);
|
|
857
1703
|
}
|
|
@@ -859,7 +1705,7 @@ const resizeVideo = function(video, width, height) {
|
|
|
859
1705
|
console.log("Resize from " + video.videoWidth + "x" + video.videoHeight + " to " + display.offsetWidth + "x" + display.offsetHeight);
|
|
860
1706
|
}
|
|
861
1707
|
|
|
862
|
-
const downScaleToFitSize = function(videoWidth, videoHeight, dstWidth, dstHeight) {
|
|
1708
|
+
const downScaleToFitSize = function (videoWidth, videoHeight, dstWidth, dstHeight) {
|
|
863
1709
|
var newWidth, newHeight;
|
|
864
1710
|
var videoRatio = videoWidth / videoHeight;
|
|
865
1711
|
var dstRatio = dstWidth / dstHeight;
|
|
@@ -876,30 +1722,30 @@ const downScaleToFitSize = function(videoWidth, videoHeight, dstWidth, dstHeight
|
|
|
876
1722
|
};
|
|
877
1723
|
}
|
|
878
1724
|
|
|
879
|
-
const createInfoDisplay = function(parent, text) {
|
|
1725
|
+
const createInfoDisplay = function (parent, text) {
|
|
880
1726
|
const div = document.createElement("div");
|
|
881
1727
|
if (text) {
|
|
882
1728
|
div.innerHTML = text;
|
|
883
1729
|
}
|
|
884
|
-
div.setAttribute("style","width:auto; height:30px;");
|
|
885
|
-
div.setAttribute("class","text-center");
|
|
1730
|
+
div.setAttribute("style", "width:auto; height:30px;");
|
|
1731
|
+
div.setAttribute("class", "text-center");
|
|
886
1732
|
if (parent) {
|
|
887
1733
|
parent.appendChild(div);
|
|
888
1734
|
}
|
|
889
1735
|
return div;
|
|
890
1736
|
}
|
|
891
1737
|
|
|
892
|
-
const createContainer = function(parent) {
|
|
1738
|
+
const createContainer = function (parent) {
|
|
893
1739
|
const div = document.createElement("div");
|
|
894
|
-
div.setAttribute("style","width:auto; height:auto;");
|
|
895
|
-
div.setAttribute("class","text-center");
|
|
1740
|
+
div.setAttribute("style", "width:auto; height:auto;");
|
|
1741
|
+
div.setAttribute("class", "text-center");
|
|
896
1742
|
if (parent) {
|
|
897
1743
|
parent.appendChild(div);
|
|
898
1744
|
}
|
|
899
1745
|
return div;
|
|
900
1746
|
}
|
|
901
1747
|
|
|
902
|
-
const createQualityButton = function(qualityName, buttonsList, parent) {
|
|
1748
|
+
const createQualityButton = function (qualityName, buttonsList, parent) {
|
|
903
1749
|
const div = document.createElement("button");
|
|
904
1750
|
div.innerText = qualityName;
|
|
905
1751
|
div.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
|
|
@@ -913,7 +1759,7 @@ const createQualityButton = function(qualityName, buttonsList, parent) {
|
|
|
913
1759
|
return div;
|
|
914
1760
|
}
|
|
915
1761
|
|
|
916
|
-
const setQualityButtonsColor = function(qualityDivs) {
|
|
1762
|
+
const setQualityButtonsColor = function (qualityDivs) {
|
|
917
1763
|
for (let c = 0; c < qualityDivs.length; c++) {
|
|
918
1764
|
if (qualityDivs[c].style.color !== QUALITY_COLORS.UNAVAILABLE) {
|
|
919
1765
|
qualityDivs[c].style.color = QUALITY_COLORS.AVAILABLE;
|
|
@@ -922,13 +1768,13 @@ const setQualityButtonsColor = function(qualityDivs) {
|
|
|
922
1768
|
}
|
|
923
1769
|
|
|
924
1770
|
// Helper functions to display/hide an element
|
|
925
|
-
const showItem = function(tag) {
|
|
1771
|
+
const showItem = function (tag) {
|
|
926
1772
|
if (tag) {
|
|
927
1773
|
tag.style.display = "block";
|
|
928
1774
|
}
|
|
929
1775
|
}
|
|
930
1776
|
|
|
931
|
-
const hideItem = function(tag) {
|
|
1777
|
+
const hideItem = function (tag) {
|
|
932
1778
|
if (tag) {
|
|
933
1779
|
tag.style.display = "none";
|
|
934
1780
|
}
|