@flashphoner/sfusdk-examples 2.0.192 → 2.0.196
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 +2 -2
- package/src/client/controls.js +160 -129
- package/src/client/display.js +4 -4
- package/src/client/main.html +1 -1
- package/src/client/main.js +159 -54
- package/src/commons/js/config.js +11 -4
- package/src/commons/js/display.js +2 -2
- package/src/player/player.js +24 -10
- package/src/sfu.ts +27 -14
- package/src/two-way-streaming/config.json +3 -1
- package/src/two-way-streaming/two-way-streaming.js +24 -11
- package/src/webrtc-abr-player/player.js +25 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flashphoner/sfusdk-examples",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.196",
|
|
4
4
|
"description": "Official Flashphoner WebCallServer SFU SDK usage examples",
|
|
5
5
|
"main": "dist/sfu.js",
|
|
6
6
|
"types": "src/sfu.ts",
|
|
@@ -27,6 +27,6 @@
|
|
|
27
27
|
"webpack-cli": "^4.9.2"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@flashphoner/sfusdk": "^2.0.
|
|
30
|
+
"@flashphoner/sfusdk": "^2.0.193"
|
|
31
31
|
}
|
|
32
32
|
}
|
package/src/client/chat.js
CHANGED
|
@@ -22,10 +22,10 @@ const createChat = function(room, messages, input, sendButton) {
|
|
|
22
22
|
}, chatOtherColour, chatEventColour);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
const sendMessage = function() {
|
|
25
|
+
const sendMessage = async function() {
|
|
26
26
|
let message = input.value;
|
|
27
27
|
input.value = "";
|
|
28
|
-
room.sendMessage(message);
|
|
28
|
+
await room.sendMessage(message);
|
|
29
29
|
appendMessage({
|
|
30
30
|
nickName: nickName.value,
|
|
31
31
|
message: message
|
package/src/client/controls.js
CHANGED
|
@@ -70,64 +70,62 @@ const createControls = function(config) {
|
|
|
70
70
|
controls.entrance.roomPin.value = config.room.pin;
|
|
71
71
|
controls.entrance.nickName.value = config.room.nickName;
|
|
72
72
|
|
|
73
|
-
const addAudioTrackRow = function(track) {
|
|
74
|
-
getMedia([track])
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
const addAudioTrackRow = async function(track) {
|
|
74
|
+
const stream = await getMedia([track]);
|
|
75
|
+
let button = '<button id="' + stream.id + '-button" class="btn btn-primary">Delete</button>';
|
|
76
|
+
const row = controls.tables.audio.row.add({
|
|
77
|
+
source: track.source,
|
|
78
|
+
channels: track.channels,
|
|
79
|
+
action: button,
|
|
80
|
+
stream: stream
|
|
81
|
+
}).node();
|
|
82
|
+
controls.tables.audio.draw();
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
});
|
|
84
|
+
$('#' + stream.id + "-button").on('click', function(){
|
|
85
|
+
//terminate stream
|
|
86
|
+
console.log("terminate audio stream " + stream.id);
|
|
87
|
+
let track = stream.getAudioTracks()[0];
|
|
88
|
+
track.stop();
|
|
89
|
+
track.dispatchEvent(new Event("ended"));
|
|
90
|
+
}).prop('disabled', true);
|
|
91
|
+
stream.getTracks()[0].onended = function() {
|
|
92
|
+
controls.tables.audio.row(row).remove().draw();
|
|
93
|
+
}
|
|
94
|
+
trackCallback({
|
|
95
|
+
stream: stream,
|
|
96
|
+
encodings: track.encodings,
|
|
97
|
+
source: track.source
|
|
99
98
|
});
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
const addVideoTrackRow = function(track) {
|
|
103
|
-
getMedia([track])
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
101
|
+
const addVideoTrackRow = async function(track) {
|
|
102
|
+
const stream = await getMedia([track]);
|
|
103
|
+
let button = '<button id="' + stream.id + '-button" class="btn btn-primary">Delete</button>';
|
|
104
|
+
const row = controls.tables.video.row.add({
|
|
105
|
+
source: track.source,
|
|
106
|
+
width: track.width,
|
|
107
|
+
height: track.height,
|
|
108
|
+
codec: track.codec,
|
|
109
|
+
action: button,
|
|
110
|
+
stream: stream,
|
|
111
|
+
encodings: track.encodings,
|
|
112
|
+
}).node();
|
|
113
|
+
controls.tables.video.draw();
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
});
|
|
115
|
+
$('#' + stream.id + "-button").on('click', function(){
|
|
116
|
+
//terminate stream
|
|
117
|
+
console.log("terminate video stream " + stream.id);
|
|
118
|
+
let track = stream.getVideoTracks()[0];
|
|
119
|
+
track.stop();
|
|
120
|
+
track.dispatchEvent(new Event("ended"));
|
|
121
|
+
}).prop('disabled', true);
|
|
122
|
+
stream.getTracks()[0].addEventListener("ended", function() {
|
|
123
|
+
controls.tables.video.row(row).remove().draw();
|
|
124
|
+
});
|
|
125
|
+
trackCallback({
|
|
126
|
+
stream: stream,
|
|
127
|
+
encodings: track.encodings,
|
|
128
|
+
source: track.source
|
|
131
129
|
});
|
|
132
130
|
}
|
|
133
131
|
|
|
@@ -148,28 +146,6 @@ const createControls = function(config) {
|
|
|
148
146
|
return details;
|
|
149
147
|
}
|
|
150
148
|
|
|
151
|
-
// Add event listener for opening and closing details
|
|
152
|
-
$('#videoTracksTableBody').on('click', 'td.details-control', function () {
|
|
153
|
-
let tr = $(this).closest('tr');
|
|
154
|
-
let row = controls.tables.video.row(tr);
|
|
155
|
-
if (row.child.isShown()) {
|
|
156
|
-
// This row is already open - close it
|
|
157
|
-
row.child.hide();
|
|
158
|
-
tr.removeClass('shown');
|
|
159
|
-
} else {
|
|
160
|
-
// Open this row
|
|
161
|
-
row.child(format(row.data())).show();
|
|
162
|
-
tr.addClass('shown');
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
config.media.audio.tracks.forEach(function(track){
|
|
167
|
-
addAudioTrackRow(track);
|
|
168
|
-
})
|
|
169
|
-
config.media.video.tracks.forEach(function(track){
|
|
170
|
-
addVideoTrackRow(track);
|
|
171
|
-
})
|
|
172
|
-
|
|
173
149
|
const muteForm = function(form) {
|
|
174
150
|
for (const [key, value] of Object.entries(form)) {
|
|
175
151
|
value.disabled = true;
|
|
@@ -187,12 +163,19 @@ const createControls = function(config) {
|
|
|
187
163
|
}
|
|
188
164
|
|
|
189
165
|
const roomConfig = function() {
|
|
190
|
-
|
|
166
|
+
let roomConfig = {
|
|
191
167
|
url: controls.entrance.url.value,
|
|
192
168
|
roomName: controls.entrance.roomName.value,
|
|
193
169
|
pin: controls.entrance.roomPin.value,
|
|
194
170
|
nickname: controls.entrance.nickName.value
|
|
171
|
+
};
|
|
172
|
+
if (config.room.failedProbesThreshold !== undefined) {
|
|
173
|
+
roomConfig.failedProbesThreshold = config.room.failedProbesThreshold;
|
|
174
|
+
}
|
|
175
|
+
if (config.room.pingInterval !== undefined) {
|
|
176
|
+
roomConfig.pingInterval = config.room.pingInterval;
|
|
195
177
|
}
|
|
178
|
+
return roomConfig;
|
|
196
179
|
}
|
|
197
180
|
|
|
198
181
|
const getVideoStreams = function() {
|
|
@@ -220,63 +203,100 @@ const createControls = function(config) {
|
|
|
220
203
|
return streams;
|
|
221
204
|
}
|
|
222
205
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
let encoding = this.data();
|
|
227
|
-
encodings.push({
|
|
228
|
-
rid: encoding.rid,
|
|
229
|
-
active: encoding.active,
|
|
230
|
-
maxBitrate: encoding.maxBitrate,
|
|
231
|
-
scaleResolutionDownBy: encoding.resolutionScale
|
|
232
|
-
})
|
|
233
|
-
});
|
|
234
|
-
let track = {
|
|
235
|
-
source: controls.addVideoTrack.source.value,
|
|
236
|
-
width: controls.addVideoTrack.width.value,
|
|
237
|
-
height: controls.addVideoTrack.height.value,
|
|
238
|
-
codec: controls.addVideoTrack.codec.value,
|
|
239
|
-
encodings: encodings
|
|
240
|
-
}
|
|
241
|
-
addVideoTrackRow(track);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
$("#videoTrackEncodingsTable").on("click", ".remove", function(){
|
|
245
|
-
controls.tables.encodings.row($(this).parents('tr')).remove().draw();
|
|
246
|
-
});
|
|
206
|
+
const onTrack = function(callback) {
|
|
207
|
+
trackCallback = callback;
|
|
208
|
+
}
|
|
247
209
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
210
|
+
const displayTables = async function() {
|
|
211
|
+
// Add event listener for opening and closing details
|
|
212
|
+
$('#videoTracksTableBody').on('click', 'td.details-control', function () {
|
|
213
|
+
let tr = $(this).closest('tr');
|
|
214
|
+
let row = controls.tables.video.row(tr);
|
|
215
|
+
if (row.child.isShown()) {
|
|
216
|
+
// This row is already open - close it
|
|
217
|
+
row.child.hide();
|
|
218
|
+
tr.removeClass('shown');
|
|
219
|
+
} else {
|
|
220
|
+
// Open this row
|
|
221
|
+
row.child(format(row.data())).show();
|
|
222
|
+
tr.addClass('shown');
|
|
223
|
+
}
|
|
224
|
+
});
|
|
258
225
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
226
|
+
// Add preconfigured audio and video tracks
|
|
227
|
+
for (const track of config.media.audio.tracks) {
|
|
228
|
+
await addAudioTrackRow(track);
|
|
229
|
+
}
|
|
230
|
+
for (const track of config.media.video.tracks) {
|
|
231
|
+
await addVideoTrackRow(track);
|
|
265
232
|
}
|
|
266
|
-
addAudioTrackRow(track);
|
|
267
|
-
});
|
|
268
233
|
|
|
269
|
-
|
|
270
|
-
|
|
234
|
+
// Click event listener to add a new video track
|
|
235
|
+
document.getElementById("addVideoTrack").addEventListener("click", function(e){
|
|
236
|
+
let encodings = [];
|
|
237
|
+
controls.tables.encodings.rows().every(function() {
|
|
238
|
+
let encoding = this.data();
|
|
239
|
+
encodings.push({
|
|
240
|
+
rid: encoding.rid,
|
|
241
|
+
active: encoding.active,
|
|
242
|
+
maxBitrate: encoding.maxBitrate,
|
|
243
|
+
scaleResolutionDownBy: encoding.resolutionScale
|
|
244
|
+
})
|
|
245
|
+
});
|
|
246
|
+
let track = {
|
|
247
|
+
source: controls.addVideoTrack.source.value,
|
|
248
|
+
width: controls.addVideoTrack.width.value,
|
|
249
|
+
height: controls.addVideoTrack.height.value,
|
|
250
|
+
codec: controls.addVideoTrack.codec.value,
|
|
251
|
+
encodings: encodings
|
|
252
|
+
}
|
|
253
|
+
addVideoTrackRow(track);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Click event listener to remove video quality
|
|
257
|
+
$("#videoTrackEncodingsTable").on("click", ".remove", function(){
|
|
258
|
+
controls.tables.encodings.row($(this).parents('tr')).remove().draw();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Click event listener to add video quality
|
|
262
|
+
document.getElementById("addVideoTrackEncoding").addEventListener("click", function(){
|
|
263
|
+
let button = '<button class="btn btn-primary remove">Delete</button>';
|
|
264
|
+
controls.tables.encodings.row.add({
|
|
265
|
+
rid: controls.addVideoEncoding.rid.value,
|
|
266
|
+
active: controls.addVideoEncoding.active.value,
|
|
267
|
+
maxBitrate: controls.addVideoEncoding.maxBitrate.value,
|
|
268
|
+
resolutionScale: controls.addVideoEncoding.resolutionScale.value,
|
|
269
|
+
action: button
|
|
270
|
+
}).draw();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Click event listener to add a new audio track
|
|
274
|
+
document.getElementById("addAudioTrack").addEventListener("click", function(e){
|
|
275
|
+
let encodings = [];
|
|
276
|
+
let track = {
|
|
277
|
+
source: controls.addAudioTrack.source.value,
|
|
278
|
+
channels: controls.addAudioTrack.channels.value,
|
|
279
|
+
encodings: encodings
|
|
280
|
+
}
|
|
281
|
+
addAudioTrackRow(track);
|
|
282
|
+
});
|
|
283
|
+
|
|
271
284
|
}
|
|
272
285
|
|
|
286
|
+
const cleanTables = function() {
|
|
287
|
+
controls.tables.video.rows().remove().draw();
|
|
288
|
+
controls.tables.audio.rows().remove().draw();
|
|
289
|
+
controls.tables.encodings.rows().remove().draw();
|
|
290
|
+
}
|
|
273
291
|
|
|
274
292
|
return {
|
|
275
293
|
muteInput: muteInput,
|
|
276
294
|
roomConfig: roomConfig,
|
|
295
|
+
displayTables: displayTables,
|
|
277
296
|
getAudioStreams: getAudioStreams,
|
|
278
297
|
getVideoStreams: getVideoStreams,
|
|
279
|
-
onTrack: onTrack
|
|
298
|
+
onTrack: onTrack,
|
|
299
|
+
cleanTables: cleanTables
|
|
280
300
|
}
|
|
281
301
|
}
|
|
282
302
|
|
|
@@ -288,17 +308,28 @@ const getMedia = async function(tracks) {
|
|
|
288
308
|
if (track.source === "mic") {
|
|
289
309
|
//audio
|
|
290
310
|
constraints.audio = {};
|
|
311
|
+
if (track.constraints) {
|
|
312
|
+
constraints.audio = track.constraints;
|
|
313
|
+
}
|
|
291
314
|
constraints.audio.stereo = track.channels !== 1
|
|
315
|
+
if (track.channels && track.channels === 2) {
|
|
316
|
+
constraints.audio.echoCancellation = false;
|
|
317
|
+
constraints.audio.googEchoCancellation = false;
|
|
318
|
+
}
|
|
292
319
|
} else if (track.source === "camera") {
|
|
293
|
-
constraints.video = {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
320
|
+
constraints.video = {};
|
|
321
|
+
if (track.constraints) {
|
|
322
|
+
constraints.video = track.constraints;
|
|
323
|
+
}
|
|
324
|
+
constraints.video.width = track.width;
|
|
325
|
+
constraints.video.height = track.height;
|
|
297
326
|
} else if (track.source === "screen") {
|
|
298
|
-
constraints.video = {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
327
|
+
constraints.video = {};
|
|
328
|
+
if (track.constraints) {
|
|
329
|
+
constraints.video = track.constraints;
|
|
330
|
+
}
|
|
331
|
+
constraints.video.width = track.width;
|
|
332
|
+
constraints.video.height = track.height;
|
|
302
333
|
screen = true;
|
|
303
334
|
}
|
|
304
335
|
});
|
package/src/client/display.js
CHANGED
|
@@ -327,7 +327,7 @@ const initRemoteDisplay = function(room, mainDiv, peerConnection) {
|
|
|
327
327
|
qualityDiv.innerText = trackInfo.quality[i];
|
|
328
328
|
qualityDiv.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
|
|
329
329
|
qualityDiv.style.color = "red";
|
|
330
|
-
qualityDiv.addEventListener('click', function(){
|
|
330
|
+
qualityDiv.addEventListener('click', async function(){
|
|
331
331
|
console.log("Clicked on quality " + trackInfo.quality[i] + " trackId " + trackInfo.id);
|
|
332
332
|
if (qualityDiv.style.color === "red") {
|
|
333
333
|
return;
|
|
@@ -338,7 +338,7 @@ const initRemoteDisplay = function(room, mainDiv, peerConnection) {
|
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
qualityDiv.style.color = "blue";
|
|
341
|
-
room.changeQuality(trackInfo.id, trackInfo.quality[i]);
|
|
341
|
+
await room.changeQuality(trackInfo.id, trackInfo.quality[i]);
|
|
342
342
|
});
|
|
343
343
|
qualityDisplay.appendChild(qualityDiv);
|
|
344
344
|
}
|
|
@@ -348,13 +348,13 @@ const initRemoteDisplay = function(room, mainDiv, peerConnection) {
|
|
|
348
348
|
tidDiv.innerText = "TID"+i;
|
|
349
349
|
tidDiv.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
|
|
350
350
|
tidDiv.style.color = "gray";
|
|
351
|
-
tidDiv.addEventListener('click', function(){
|
|
351
|
+
tidDiv.addEventListener('click', async function(){
|
|
352
352
|
console.log("Clicked on TID " + i + " trackId " + trackInfo.id);
|
|
353
353
|
for (let c = 0; c < tidDivs.length; c++) {
|
|
354
354
|
tidDivs[c].style.color = "gray";
|
|
355
355
|
}
|
|
356
356
|
tidDiv.style.color = "blue";
|
|
357
|
-
room.changeQuality(trackInfo.id, null, i);
|
|
357
|
+
await room.changeQuality(trackInfo.id, null, i);
|
|
358
358
|
});
|
|
359
359
|
tidDisplay.appendChild(tidDiv);
|
|
360
360
|
}
|
package/src/client/main.html
CHANGED
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
</div>
|
|
211
211
|
</div>
|
|
212
212
|
<div class="modal-footer">
|
|
213
|
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
213
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" onclick="cancel()">Close</button>
|
|
214
214
|
<button class="btn btn-primary" id="startButton" onclick="connect()">Enter</button>
|
|
215
215
|
</div>
|
|
216
216
|
</div>
|
package/src/client/main.js
CHANGED
|
@@ -2,7 +2,6 @@ const constants = SFU.constants;
|
|
|
2
2
|
const sfu = SFU;
|
|
3
3
|
let localDisplay;
|
|
4
4
|
let cControls;
|
|
5
|
-
let pc;
|
|
6
5
|
|
|
7
6
|
const defaultConfig = {
|
|
8
7
|
room: {
|
|
@@ -38,7 +37,7 @@ const defaultConfig = {
|
|
|
38
37
|
};
|
|
39
38
|
|
|
40
39
|
/**
|
|
41
|
-
*
|
|
40
|
+
* Load track configuration and show entrance modal
|
|
42
41
|
*/
|
|
43
42
|
const init = function() {
|
|
44
43
|
//read config
|
|
@@ -48,44 +47,57 @@ const init = function() {
|
|
|
48
47
|
//use default config
|
|
49
48
|
cControls = createControls(defaultConfig);
|
|
50
49
|
});
|
|
51
|
-
//create local display to show local streams
|
|
52
|
-
localDisplay = initLocalDisplay(document.getElementById("localDisplay"));
|
|
53
50
|
//open entrance modal
|
|
54
51
|
$('#entranceModal').modal('show');
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
/**
|
|
58
|
-
*
|
|
55
|
+
* Connect to server and publish preconfigured streams
|
|
59
56
|
*/
|
|
60
|
-
function connect() {
|
|
61
|
-
//hide modal
|
|
57
|
+
async function connect() {
|
|
58
|
+
// hide modal
|
|
62
59
|
$('#entranceModal').modal('hide');
|
|
63
|
-
//disable controls
|
|
60
|
+
// disable controls
|
|
64
61
|
cControls.muteInput();
|
|
65
62
|
//create peer connection
|
|
66
|
-
pc = new RTCPeerConnection();
|
|
63
|
+
const pc = new RTCPeerConnection();
|
|
67
64
|
//get config object for room creation
|
|
68
65
|
const roomConfig = cControls.roomConfig();
|
|
69
66
|
//kick off connect to server and local room creation
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
try {
|
|
68
|
+
const session = await sfu.createRoom(roomConfig);
|
|
69
|
+
// Now we connected to the server (if no exception was thrown)
|
|
70
|
+
session.on(constants.SFU_EVENT.FAILED, function(e) {
|
|
71
|
+
if (e.status && e.statusText) {
|
|
72
|
+
displayError("CONNECTION FAILED: " + e.status + " " + e.statusText);
|
|
73
|
+
} else if (e.type && e.info) {
|
|
74
|
+
displayError("CONNECTION FAILED: " + e.info);
|
|
75
|
+
} else {
|
|
76
|
+
displayError("CONNECTION FAILED: " + e);
|
|
77
|
+
}
|
|
78
|
+
}).on(constants.SFU_EVENT.DISCONNECTED, function(e) {
|
|
79
|
+
displayError("DISCONNECTED. Refresh the page to enter the room again");
|
|
80
|
+
});
|
|
72
81
|
const room = session.room();
|
|
73
|
-
|
|
82
|
+
room.on(constants.SFU_ROOM_EVENT.FAILED, function(e) {
|
|
83
|
+
displayError(e);
|
|
84
|
+
}).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
|
|
85
|
+
displayError(e.operation + " failed: " + e.error);
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// create local display to show local streams
|
|
89
|
+
localDisplay = initLocalDisplay(document.getElementById("localDisplay"));
|
|
90
|
+
// display audio and video control tables
|
|
91
|
+
await cControls.displayTables();
|
|
92
|
+
cControls.onTrack(async function (s) {
|
|
93
|
+
await publishNewTrack(room, pc, s);
|
|
94
|
+
});
|
|
95
|
+
//create and bind chat to the new room
|
|
74
96
|
const chatDiv = document.getElementById('messages');
|
|
75
97
|
const chatInput = document.getElementById('localMessage');
|
|
76
98
|
const chatButton = document.getElementById('sendMessage');
|
|
77
|
-
//create and bind chat to the new room
|
|
78
99
|
createChat(room, chatDiv, chatInput, chatButton);
|
|
79
100
|
|
|
80
|
-
room.on(constants.SFU_ROOM_EVENT.FAILED, function(e) {
|
|
81
|
-
const errField = document.getElementById("errorMsg");
|
|
82
|
-
errField.style.color = "red";
|
|
83
|
-
errField.innerText = e;
|
|
84
|
-
}).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
|
|
85
|
-
const errField = document.getElementById("errorMsg");
|
|
86
|
-
errField.style.color = "red";
|
|
87
|
-
errField.innerText = e.operation + " failed: " + e.error;
|
|
88
|
-
})
|
|
89
101
|
//setup remote display for showing remote audio/video tracks
|
|
90
102
|
const remoteDisplay = document.getElementById("display");
|
|
91
103
|
initRemoteDisplay(room, remoteDisplay, pc);
|
|
@@ -94,6 +106,25 @@ function connect() {
|
|
|
94
106
|
let streams = cControls.getVideoStreams();
|
|
95
107
|
//combine local video streams with audio streams
|
|
96
108
|
streams.push.apply(streams, cControls.getAudioStreams());
|
|
109
|
+
|
|
110
|
+
// Publish preconfigured streams
|
|
111
|
+
publishPreconfiguredStreams(room, pc, streams);
|
|
112
|
+
} catch(e) {
|
|
113
|
+
console.error(e);
|
|
114
|
+
displayError(e);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Publish streams after entyering room according to configuration file
|
|
121
|
+
*
|
|
122
|
+
* @param {*} room
|
|
123
|
+
* @param {*} pc
|
|
124
|
+
* @param {*} streams
|
|
125
|
+
*/
|
|
126
|
+
const publishPreconfiguredStreams = async function(room, pc, streams) {
|
|
127
|
+
try {
|
|
97
128
|
let config = {};
|
|
98
129
|
//add our local streams to the room (to PeerConnection)
|
|
99
130
|
streams.forEach(function (s) {
|
|
@@ -108,50 +139,124 @@ function connect() {
|
|
|
108
139
|
subscribeTrackToEndedEvent(room, track, pc);
|
|
109
140
|
});
|
|
110
141
|
});
|
|
111
|
-
//add callback for the new local stream to the local controls
|
|
112
|
-
cControls.onTrack(function (s) {
|
|
113
|
-
let config = {};
|
|
114
|
-
//add local stream to local display
|
|
115
|
-
localDisplay.add(s.stream.id, "local", s.stream);
|
|
116
|
-
//add each track to PeerConnection
|
|
117
|
-
s.stream.getTracks().forEach((track) => {
|
|
118
|
-
if (s.source === "screen") {
|
|
119
|
-
config[track.id] = s.source;
|
|
120
|
-
}
|
|
121
|
-
addTrackToPeerConnection(pc, s.stream, track, s.encodings);
|
|
122
|
-
subscribeTrackToEndedEvent(room, track, pc);
|
|
123
|
-
});
|
|
124
|
-
//kickoff renegotiation
|
|
125
|
-
room.updateState(config);
|
|
126
|
-
});
|
|
127
142
|
//join room
|
|
128
|
-
room.join(pc, null, config);
|
|
129
|
-
|
|
143
|
+
await room.join(pc, null, config);
|
|
144
|
+
// Enable Delete button for each preconfigured stream #WCS-3689
|
|
145
|
+
streams.forEach(function (s) {
|
|
146
|
+
$('#' + s.stream.id + "-button").prop('disabled', false);
|
|
147
|
+
});
|
|
148
|
+
} catch(e) {
|
|
149
|
+
console.error("Failed to publish a preconfigured streams: " + e);
|
|
150
|
+
displayError(e);
|
|
151
|
+
// Enable Delete button for each preconfigured stream #WCS-3689
|
|
152
|
+
streams.forEach(function (s) {
|
|
153
|
+
$('#' + s.stream.id + "-button").prop('disabled', false);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
130
156
|
}
|
|
131
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Publish a new media track to the room
|
|
160
|
+
*
|
|
161
|
+
* @param {*} room
|
|
162
|
+
* @param {*} pc
|
|
163
|
+
* @param {*} media
|
|
164
|
+
*/
|
|
165
|
+
const publishNewTrack = async function(room, pc, media) {
|
|
166
|
+
try {
|
|
167
|
+
let config = {};
|
|
168
|
+
//add local stream to local display
|
|
169
|
+
localDisplay.add(media.stream.id, "local", media.stream);
|
|
170
|
+
//add each track to PeerConnection
|
|
171
|
+
media.stream.getTracks().forEach((track) => {
|
|
172
|
+
if (media.source === "screen") {
|
|
173
|
+
config[track.id] = media.source;
|
|
174
|
+
}
|
|
175
|
+
addTrackToPeerConnection(pc, media.stream, track, media.encodings);
|
|
176
|
+
subscribeTrackToEndedEvent(room, track, pc);
|
|
177
|
+
});
|
|
178
|
+
// Clean error message
|
|
179
|
+
displayError("");
|
|
180
|
+
//kickoff renegotiation
|
|
181
|
+
await room.updateState(config);
|
|
182
|
+
// Enable Delete button for a new stream #WCS-3689
|
|
183
|
+
$('#' + media.stream.id + "-button").prop('disabled', false);
|
|
184
|
+
} catch(e) {
|
|
185
|
+
console.error("Failed to publish a new track: " + e);
|
|
186
|
+
displayError(e);
|
|
187
|
+
// Enable Delete button for a new stream #WCS-3689
|
|
188
|
+
$('#' + media.stream.id + "-button").prop('disabled', false);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Subscribe to track ended event to renegotiate WebRTC connection
|
|
194
|
+
*
|
|
195
|
+
* @param {*} room
|
|
196
|
+
* @param {*} track
|
|
197
|
+
* @param {*} pc
|
|
198
|
+
*/
|
|
132
199
|
const subscribeTrackToEndedEvent = function(room, track, pc) {
|
|
133
|
-
track.addEventListener("ended", function() {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
200
|
+
track.addEventListener("ended", async function() {
|
|
201
|
+
try {
|
|
202
|
+
//track ended, see if we need to cleanup
|
|
203
|
+
let negotiate = false;
|
|
204
|
+
for (const sender of pc.getSenders()) {
|
|
205
|
+
if (sender.track === track) {
|
|
206
|
+
pc.removeTrack(sender);
|
|
207
|
+
//track found, set renegotiation flag
|
|
208
|
+
negotiate = true;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
142
211
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
212
|
+
// Clean error message
|
|
213
|
+
displayError("");
|
|
214
|
+
if (negotiate) {
|
|
215
|
+
//kickoff renegotiation
|
|
216
|
+
await room.updateState();
|
|
217
|
+
}
|
|
218
|
+
} catch(e) {
|
|
219
|
+
displayError(e);
|
|
220
|
+
console.error("Failed to update room state: " + e);
|
|
147
221
|
}
|
|
148
222
|
});
|
|
149
|
-
}
|
|
223
|
+
}
|
|
150
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Add track to WebRTC PeerConnection
|
|
227
|
+
*
|
|
228
|
+
* @param {*} pc
|
|
229
|
+
* @param {*} stream
|
|
230
|
+
* @param {*} track
|
|
231
|
+
* @param {*} encodings
|
|
232
|
+
*/
|
|
151
233
|
const addTrackToPeerConnection = function(pc, stream, track, encodings) {
|
|
152
234
|
pc.addTransceiver(track, {
|
|
153
235
|
direction: "sendonly",
|
|
154
236
|
streams: [stream],
|
|
155
237
|
sendEncodings: encodings ? encodings : [] //passing encoding types for video simulcast tracks
|
|
156
238
|
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Display error message
|
|
243
|
+
*
|
|
244
|
+
* @param {*} text
|
|
245
|
+
*/
|
|
246
|
+
const displayError = function(text) {
|
|
247
|
+
const errField = document.getElementById("errorMsg");
|
|
248
|
+
errField.style.color = "red";
|
|
249
|
+
errField.innerText = text;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Entrance modal cancelled, we do not enter to a room
|
|
254
|
+
*/
|
|
255
|
+
const cancel = function() {
|
|
256
|
+
//hide modal
|
|
257
|
+
$('#entranceModal').modal('hide');
|
|
258
|
+
//disable controls
|
|
259
|
+
cControls.muteInput();
|
|
260
|
+
// display the error message
|
|
261
|
+
displayError("Please refresh the page, fill the entrance modal and enter a room to publish or play streams");
|
|
157
262
|
}
|
package/src/commons/js/config.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
const getRoomConfig = function(config) {
|
|
2
2
|
let roomConfig = {
|
|
3
|
-
url: config.url || "ws://127.0.0.1:8080",
|
|
4
|
-
roomName: config.name || "ROOM1",
|
|
5
|
-
pin: config.pin || "1234",
|
|
6
|
-
nickname: config.nickName || "User1"
|
|
3
|
+
url: config.room.url || "ws://127.0.0.1:8080",
|
|
4
|
+
roomName: config.room.name || "ROOM1",
|
|
5
|
+
pin: config.room.pin || "1234",
|
|
6
|
+
nickname: config.room.nickName || "User1"
|
|
7
7
|
};
|
|
8
|
+
if (config.room.failedProbesThreshold !== undefined) {
|
|
9
|
+
roomConfig.failedProbesThreshold = config.room.failedProbesThreshold;
|
|
10
|
+
}
|
|
11
|
+
if (config.room.pingInterval !== undefined) {
|
|
12
|
+
roomConfig.pingInterval = config.room.pingInterval;
|
|
13
|
+
}
|
|
8
14
|
return roomConfig;
|
|
9
15
|
}
|
|
10
16
|
|
|
@@ -50,6 +56,7 @@ const getMedia = async function(track) {
|
|
|
50
56
|
if (track.constraints) {
|
|
51
57
|
constraints.audio = track.constraints;
|
|
52
58
|
}
|
|
59
|
+
constraints.audio.stereo = track.channels !== 1
|
|
53
60
|
if (track.channels && track.channels === 2) {
|
|
54
61
|
constraints.audio.echoCancellation = false;
|
|
55
62
|
constraints.audio.googEchoCancellation = false;
|
|
@@ -391,7 +391,7 @@ const initRemoteDisplay = function(options) {
|
|
|
391
391
|
qualityDiv.innerText = trackInfo.quality[i];
|
|
392
392
|
qualityDiv.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
|
|
393
393
|
qualityDiv.style.color = "red";
|
|
394
|
-
qualityDiv.addEventListener('click', function(){
|
|
394
|
+
qualityDiv.addEventListener('click', async function(){
|
|
395
395
|
console.log("Clicked on quality " + trackInfo.quality[i] + " trackId " + trackInfo.id);
|
|
396
396
|
if (qualityDiv.style.color === "red") {
|
|
397
397
|
return;
|
|
@@ -402,7 +402,7 @@ const initRemoteDisplay = function(options) {
|
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
404
|
qualityDiv.style.color = "blue";
|
|
405
|
-
room.changeQuality(trackInfo.id, trackInfo.quality[i]);
|
|
405
|
+
await room.changeQuality(trackInfo.id, trackInfo.quality[i]);
|
|
406
406
|
});
|
|
407
407
|
qualitySwitchDisplay.appendChild(qualityDiv);
|
|
408
408
|
}
|
package/src/player/player.js
CHANGED
|
@@ -155,7 +155,7 @@ const init = function() {
|
|
|
155
155
|
/**
|
|
156
156
|
* connect to server
|
|
157
157
|
*/
|
|
158
|
-
const connect = function(state) {
|
|
158
|
+
const connect = async function(state) {
|
|
159
159
|
//create peer connection
|
|
160
160
|
pc = new RTCPeerConnection();
|
|
161
161
|
//get config object for room creation
|
|
@@ -167,21 +167,35 @@ const connect = function(state) {
|
|
|
167
167
|
setStatus(state.statusId(), "");
|
|
168
168
|
setStatus(state.errInfoId(), "");
|
|
169
169
|
// connect to server and create a room if not
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
try {
|
|
171
|
+
const session = await sfu.createRoom(roomConfig);
|
|
172
|
+
// Set up session ending events
|
|
173
|
+
session.on(constants.SFU_EVENT.DISCONNECTED, function() {
|
|
174
|
+
onStopClick(state);
|
|
175
|
+
state.clear();
|
|
176
|
+
onDisconnected(state);
|
|
177
|
+
setStatus(state.statusId(), "DISCONNECTED", "green");
|
|
178
|
+
}).on(constants.SFU_EVENT.FAILED, function(e) {
|
|
179
|
+
onStopClick(state);
|
|
180
|
+
state.clear();
|
|
181
|
+
onDisconnected(state);
|
|
182
|
+
setStatus(state.statusId(), "FAILED", "red");
|
|
183
|
+
if (e.status && e.statusText) {
|
|
184
|
+
setStatus(state.errInfoId(), e.status + " " + e.statusText, "red");
|
|
185
|
+
} else if (e.type && e.info) {
|
|
186
|
+
setStatus(state.errInfoId(), e.type + ": " + e.info, "red");
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
// Connected successfully
|
|
172
190
|
state.set(pc, session, session.room());
|
|
173
191
|
onConnected(state);
|
|
174
192
|
setStatus(state.statusId(), "ESTABLISHED", "green");
|
|
175
|
-
}
|
|
176
|
-
state.clear();
|
|
177
|
-
onDisconnected(state);
|
|
178
|
-
setStatus(state.statusId(), "DISCONNECTED", "green");
|
|
179
|
-
}).on(constants.SFU_EVENT.FAILED, function(e) {
|
|
193
|
+
} catch(e) {
|
|
180
194
|
state.clear();
|
|
181
195
|
onDisconnected(state);
|
|
182
196
|
setStatus(state.statusId(), "FAILED", "red");
|
|
183
|
-
setStatus(state.errInfoId(), e
|
|
184
|
-
}
|
|
197
|
+
setStatus(state.errInfoId(), e, "red");
|
|
198
|
+
}
|
|
185
199
|
}
|
|
186
200
|
|
|
187
201
|
const onConnected = async function(state) {
|
package/src/sfu.ts
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Sfu,
|
|
3
|
+
RoomEvent,
|
|
4
|
+
SfuEvent,
|
|
5
|
+
State
|
|
6
|
+
} from "@flashphoner/sfusdk";
|
|
2
7
|
|
|
3
|
-
export function createRoom(options: {
|
|
8
|
+
export async function createRoom(options: {
|
|
4
9
|
url: string,
|
|
5
10
|
roomName: string,
|
|
6
11
|
pin: string,
|
|
7
|
-
nickname: string
|
|
12
|
+
nickname: string,
|
|
13
|
+
failedProbesThreshold?: number,
|
|
14
|
+
pingInterval?: number,
|
|
8
15
|
}) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
try {
|
|
17
|
+
const sfu = new Sfu();
|
|
18
|
+
await sfu.connect({
|
|
19
|
+
url: options.url,
|
|
20
|
+
nickname: options.nickname,
|
|
21
|
+
logGroup: options.roomName,
|
|
22
|
+
failedProbesThreshold: options.failedProbesThreshold,
|
|
23
|
+
pingInterval: options.pingInterval
|
|
24
|
+
});
|
|
25
|
+
sfu.createRoom({
|
|
26
|
+
name: options.roomName,
|
|
27
|
+
pin: options.pin
|
|
28
|
+
});
|
|
29
|
+
return sfu;
|
|
30
|
+
} catch (e) {
|
|
31
|
+
throw new Error("Can't connect to websocket URL " + options.url);
|
|
32
|
+
}
|
|
20
33
|
}
|
|
21
34
|
|
|
22
35
|
export const constants = {
|
|
@@ -195,7 +195,7 @@ const init = function() {
|
|
|
195
195
|
/**
|
|
196
196
|
* connect to server
|
|
197
197
|
*/
|
|
198
|
-
const connect = function(state) {
|
|
198
|
+
const connect = async function(state) {
|
|
199
199
|
//create peer connection
|
|
200
200
|
let pc = new RTCPeerConnection();
|
|
201
201
|
//get config object for room creation
|
|
@@ -207,22 +207,35 @@ const connect = function(state) {
|
|
|
207
207
|
setStatus(state.statusId(), "");
|
|
208
208
|
setStatus(state.errInfoId(), "");
|
|
209
209
|
// connect to server and create a room if not
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
try {
|
|
211
|
+
const session = await sfu.createRoom(roomConfig);
|
|
212
|
+
// Set up session ending events
|
|
213
|
+
session.on(constants.SFU_EVENT.DISCONNECTED, function() {
|
|
214
|
+
onStopClick(state);
|
|
215
|
+
state.clear();
|
|
216
|
+
onDisconnected(state);
|
|
217
|
+
setStatus(state.statusId(), "DISCONNECTED", "green");
|
|
218
|
+
}).on(constants.SFU_EVENT.FAILED, function(e) {
|
|
219
|
+
onStopClick(state);
|
|
220
|
+
state.clear();
|
|
221
|
+
onDisconnected(state);
|
|
222
|
+
setStatus(state.statusId(), "FAILED", "red");
|
|
223
|
+
if (e.status && e.statusText) {
|
|
224
|
+
setStatus(state.errInfoId(), e.status + " " + e.statusText, "red");
|
|
225
|
+
} else if (e.type && e.info) {
|
|
226
|
+
setStatus(state.errInfoId(), e.type + ": " + e.info, "red");
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
// Connected successfully
|
|
212
230
|
state.set(pc, session, session.room());
|
|
213
231
|
onConnected(state);
|
|
214
232
|
setStatus(state.statusId(), "ESTABLISHED", "green");
|
|
215
|
-
}
|
|
216
|
-
state.clear();
|
|
217
|
-
onDisconnected(state);
|
|
218
|
-
setStatus(state.statusId(), "DISCONNECTED", "green");
|
|
219
|
-
}).on(constants.SFU_EVENT.FAILED, function(e) {
|
|
233
|
+
} catch(e) {
|
|
220
234
|
state.clear();
|
|
221
235
|
onDisconnected(state);
|
|
222
236
|
setStatus(state.statusId(), "FAILED", "red");
|
|
223
|
-
setStatus(state.errInfoId(), e
|
|
224
|
-
}
|
|
225
|
-
}
|
|
237
|
+
setStatus(state.errInfoId(), e, "red");
|
|
238
|
+
}}
|
|
226
239
|
|
|
227
240
|
const onConnected = function(state) {
|
|
228
241
|
$("#" + state.buttonId()).text("Stop").off('click').click(function () {
|
|
@@ -113,7 +113,7 @@ const init = function() {
|
|
|
113
113
|
/**
|
|
114
114
|
* Connect to server
|
|
115
115
|
*/
|
|
116
|
-
const connect = function(state) {
|
|
116
|
+
const connect = async function(state) {
|
|
117
117
|
// Create peer connection
|
|
118
118
|
let pc = new RTCPeerConnection();
|
|
119
119
|
// Create a config to connect to SFU room
|
|
@@ -130,22 +130,36 @@ const connect = function(state) {
|
|
|
130
130
|
// Clean state display items
|
|
131
131
|
setStatus("playStatus", "");
|
|
132
132
|
setStatus("playErrorInfo", "");
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
try {
|
|
134
|
+
// Connect to the server (room should already exist)
|
|
135
|
+
const session = await sfu.createRoom(roomConfig);
|
|
136
|
+
// Set up session ending events
|
|
137
|
+
session.on(constants.SFU_EVENT.DISCONNECTED, function() {
|
|
138
|
+
onStopClick(state);
|
|
139
|
+
state.clear();
|
|
140
|
+
onDisconnected(state);
|
|
141
|
+
setStatus("playStatus", "DISCONNECTED", "green");
|
|
142
|
+
}).on(constants.SFU_EVENT.FAILED, function(e) {
|
|
143
|
+
onStopClick(state);
|
|
144
|
+
state.clear();
|
|
145
|
+
onDisconnected(state);
|
|
146
|
+
setStatus("playStatus", "FAILED", "red");
|
|
147
|
+
if (e.status && e.statusText) {
|
|
148
|
+
setStatus("playErrorInfo", e.status + " " + e.statusText, "red");
|
|
149
|
+
} else if (e.type && e.info) {
|
|
150
|
+
setStatus("playErrorInfo", e.type + ": " + e.info, "red");
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
// Connected successfully
|
|
136
154
|
state.set(pc, session, session.room());
|
|
137
155
|
onConnected(state);
|
|
138
156
|
setStatus("playStatus", "CONNECTING...", "black");
|
|
139
|
-
}
|
|
140
|
-
state.clear();
|
|
141
|
-
onDisconnected(state);
|
|
142
|
-
setStatus("playStatus", "DISCONNECTED", "green");
|
|
143
|
-
}).on(constants.SFU_EVENT.FAILED, function(e) {
|
|
157
|
+
} catch(e) {
|
|
144
158
|
state.clear();
|
|
145
159
|
onDisconnected(state);
|
|
146
160
|
setStatus("playStatus", "FAILED", "red");
|
|
147
|
-
setStatus("playErrorInfo", e
|
|
148
|
-
}
|
|
161
|
+
setStatus("playErrorInfo", e, "red");
|
|
162
|
+
}
|
|
149
163
|
}
|
|
150
164
|
|
|
151
165
|
const onConnected = async function(state) {
|