@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flashphoner/sfusdk-examples",
3
- "version": "2.0.192",
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.130"
30
+ "@flashphoner/sfusdk": "^2.0.193"
31
31
  }
32
32
  }
@@ -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
@@ -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]).then(function(stream){
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();
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
- $('#' + stream.id + "-button").on('click', function(){
85
- //terminate stream
86
- console.log("terminate stream " + stream.id);
87
- let track = stream.getAudioTracks()[0];
88
- track.stop();
89
- track.dispatchEvent(new Event("ended"));
90
- });
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
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]).then(function(stream){
104
- let button = '<button id="' + stream.id + '-button" class="btn btn-primary">Delete</button>';
105
- const row = controls.tables.video.row.add({
106
- source: track.source,
107
- width: track.width,
108
- height: track.height,
109
- codec: track.codec,
110
- action: button,
111
- stream: stream,
112
- encodings: track.encodings
113
- }).node();
114
- controls.tables.video.draw();
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
- $('#' + stream.id + "-button").on('click', function(){
117
- //terminate stream
118
- console.log("terminate stream " + stream.id);
119
- let track = stream.getVideoTracks()[0];
120
- track.stop();
121
- track.dispatchEvent(new Event("ended"));
122
- });
123
- stream.getTracks()[0].addEventListener("ended", function() {
124
- controls.tables.video.row(row).remove().draw();
125
- });
126
- trackCallback({
127
- stream: stream,
128
- encodings: track.encodings,
129
- source: track.source
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
- return {
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
- document.getElementById("addVideoTrack").addEventListener("click", function(e){
224
- let encodings = [];
225
- controls.tables.encodings.rows().every(function() {
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
- document.getElementById("addVideoTrackEncoding").addEventListener("click", function(){
249
- let button = '<button class="btn btn-primary remove">Delete</button>';
250
- controls.tables.encodings.row.add({
251
- rid: controls.addVideoEncoding.rid.value,
252
- active: controls.addVideoEncoding.active.value,
253
- maxBitrate: controls.addVideoEncoding.maxBitrate.value,
254
- resolutionScale: controls.addVideoEncoding.resolutionScale.value,
255
- action: button
256
- }).draw();
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
- document.getElementById("addAudioTrack").addEventListener("click", function(e){
260
- let encodings = [];
261
- let track = {
262
- source: controls.addAudioTrack.source.value,
263
- channels: controls.addAudioTrack.channels.value,
264
- encodings: encodings
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
- const onTrack = function(callback) {
270
- trackCallback = callback;
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
- width: track.width,
295
- height: track.height
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
- width: track.width,
300
- height: track.height
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
  });
@@ -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
  }
@@ -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>
@@ -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
- * load config and show entrance modal
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
- * connect to server
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
- const session = sfu.createRoom(roomConfig);
71
- session.on(constants.SFU_EVENT.CONNECTED, function() {
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
- //connected to server
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
- //track ended, see if we need to cleanup
135
- let negotiate = false;
136
- for (const sender of pc.getSenders()) {
137
- if (sender.track === track) {
138
- pc.removeTrack(sender);
139
- //track found, set renegotiation flag
140
- negotiate = true;
141
- break;
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
- if (negotiate) {
145
- //kickoff renegotiation
146
- room.updateState();
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
  }
@@ -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
  }
@@ -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
- const session = sfu.createRoom(roomConfig);
171
- session.on(constants.SFU_EVENT.CONNECTED, function() {
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
- }).on(constants.SFU_EVENT.DISCONNECTED, function() {
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.status + " " + e.statusText, "red");
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 {Sfu, RoomEvent, SfuEvent, State} from "@flashphoner/sfusdk";
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
- const sfu = new Sfu();
10
- sfu.connect({
11
- url: options.url,
12
- nickname: options.nickname,
13
- logGroup: options.roomName
14
- });
15
- sfu.createRoom({
16
- name: options.roomName,
17
- pin: options.pin
18
- });
19
- return sfu;
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 = {
@@ -3,7 +3,9 @@
3
3
  "url": "ws://127.0.0.1:8080",
4
4
  "name": "ROOM1",
5
5
  "pin": "1234",
6
- "nickName": "User1"
6
+ "nickName": "User1",
7
+ "failedProbesThreshold": 5,
8
+ "pingInterval": 5000
7
9
  },
8
10
  "media": {
9
11
  "audio": {
@@ -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
- const session = sfu.createRoom(roomConfig);
211
- session.on(constants.SFU_EVENT.CONNECTED, function() {
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
- }).on(constants.SFU_EVENT.DISCONNECTED, function() {
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.status + " " + e.statusText, "red");
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
- // Connect to the server (room should already exist)
134
- const session = sfu.createRoom(roomConfig);
135
- session.on(constants.SFU_EVENT.CONNECTED, function() {
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
- }).on(constants.SFU_EVENT.DISCONNECTED, function() {
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.status + " " + e.statusText, "red");
148
- });
161
+ setStatus("playErrorInfo", e, "red");
162
+ }
149
163
  }
150
164
 
151
165
  const onConnected = async function(state) {