@flashphoner/sfusdk-examples 2.0.132 → 2.0.136

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.132",
3
+ "version": "2.0.136",
4
4
  "description": "Official Flashphoner WebCallServer SFU SDK usage examples",
5
5
  "main": "dist/sfu.js",
6
6
  "types": "src/sfu.ts",
@@ -57,11 +57,11 @@ const initLocalDisplay = function(localDisplayElement){
57
57
  coreDisplay.setAttribute("class","text-center");
58
58
  coreDisplay.setAttribute("style","width: auto; height: auto;");
59
59
  coreDisplay.id = stream.id;
60
- const streamNameDisplay = document.createElement("div");
61
- streamNameDisplay.innerHTML = "Name: " + name;
62
- streamNameDisplay.setAttribute("class","text-center");
63
- streamNameDisplay.setAttribute("style","width: auto; height: auto;");
64
- coreDisplay.appendChild(streamNameDisplay);
60
+ const publisherNameDisplay = document.createElement("div");
61
+ publisherNameDisplay.innerHTML = "Name: " + name;
62
+ publisherNameDisplay.setAttribute("class","text-center");
63
+ publisherNameDisplay.setAttribute("style","width: auto; height: auto;");
64
+ coreDisplay.appendChild(publisherNameDisplay);
65
65
 
66
66
  const audioStateDisplay = document.createElement("button");
67
67
  audioStateDisplay.innerHTML = audioStateText(stream);
@@ -102,7 +102,7 @@ const initLocalDisplay = function(localDisplayElement){
102
102
  });
103
103
  });
104
104
  video.addEventListener('resize', function (event) {
105
- streamNameDisplay.innerHTML = "Name: " + name + "<br/>Max.resolution: " + video.videoWidth + "x" + video.videoHeight;
105
+ publisherNameDisplay.innerHTML = "Name: " + name + "<br/>Max.resolution: " + video.videoWidth + "x" + video.videoHeight;
106
106
  resizeVideo(event.target);
107
107
  });
108
108
  localDisplays[id] = coreDisplay;
@@ -133,9 +133,25 @@ const initLocalDisplay = function(localDisplayElement){
133
133
  }
134
134
  }
135
135
 
136
- const initRemoteDisplay = function(mainDiv, room, peerConnection) {
136
+ const initRemoteDisplay = function(options) {
137
137
  const constants = SFU.constants;
138
138
  const remoteParticipants = {};
139
+ // Validate options first
140
+ if (!options.div) {
141
+ throw new Error("Main div to place all the media tag is not defined");
142
+ }
143
+ if (!options.room) {
144
+ throw new Error("Room is not defined");
145
+ }
146
+ if (!options.peerConnection) {
147
+ throw new Error("PeerConnection is not defined");
148
+ }
149
+
150
+ let mainDiv = options.div;
151
+ let room = options.room;
152
+ let peerConnection = options.peerConnection;
153
+ let displayOptions = options.displayOptions || {publisher: true, quality: true};
154
+
139
155
  room.on(constants.SFU_ROOM_EVENT.ADD_TRACKS, function(e) {
140
156
  console.log("Received ADD_TRACKS");
141
157
  let participant = remoteParticipants[e.info.nickName];
@@ -171,7 +187,7 @@ const initRemoteDisplay = function(mainDiv, room, peerConnection) {
171
187
  if (!createDisplay) {
172
188
  continue;
173
189
  }
174
- let display = createRemoteDisplay(participant.nickName, participant.nickName, mainDiv);
190
+ let display = createRemoteDisplay(participant.nickName, participant.nickName, mainDiv, displayOptions);
175
191
  participant.displays.push(display);
176
192
  if (pTrack.type === "VIDEO") {
177
193
  display.videoMid = pTrack.mid;
@@ -241,20 +257,31 @@ const initRemoteDisplay = function(mainDiv, room, peerConnection) {
241
257
  }
242
258
  });
243
259
 
244
- const createRemoteDisplay = function(id, name, mainDiv) {
260
+ const createRemoteDisplay = function(id, name, mainDiv, displayOptions) {
245
261
  const cell = document.createElement("div");
246
262
  cell.setAttribute("class", "text-center");
247
263
  cell.id = id;
248
264
  mainDiv.appendChild(cell);
249
- const streamNameDisplay = document.createElement("div");
250
- streamNameDisplay.innerHTML = "Published by: " + name;
251
- streamNameDisplay.setAttribute("style","width:auto; height:auto;");
252
- streamNameDisplay.setAttribute("class","text-center");
253
- cell.appendChild(streamNameDisplay);
254
- const qualityDisplay = document.createElement("div");
255
- qualityDisplay.setAttribute("style","width:auto; height:auto;");
256
- qualityDisplay.setAttribute("class","text-center");
257
- cell.appendChild(qualityDisplay);
265
+ let publisherNameDisplay;
266
+ let currentQualityDisplay;
267
+ if (displayOptions.publisher) {
268
+ publisherNameDisplay = document.createElement("div");
269
+ publisherNameDisplay.innerHTML = "Published by: " + name;
270
+ publisherNameDisplay.setAttribute("style","width:auto; height:30px;");
271
+ publisherNameDisplay.setAttribute("class","text-center");
272
+ cell.appendChild(publisherNameDisplay);
273
+ }
274
+ if (displayOptions.quality) {
275
+ currentQualityDisplay = document.createElement("div");
276
+ currentQualityDisplay.innerHTML = "";
277
+ currentQualityDisplay.setAttribute("style","width:auto; height:30px;");
278
+ currentQualityDisplay.setAttribute("class","text-center");
279
+ cell.appendChild(currentQualityDisplay);
280
+ }
281
+ const qualitySwitchDisplay = document.createElement("div");
282
+ qualitySwitchDisplay.setAttribute("style","width:auto; height:30px;");
283
+ qualitySwitchDisplay.setAttribute("class","text-center");
284
+ cell.appendChild(qualitySwitchDisplay);
258
285
 
259
286
  let qualityDivs = [];
260
287
 
@@ -293,11 +320,22 @@ const initRemoteDisplay = function(mainDiv, room, peerConnection) {
293
320
  audio.controls = "controls";
294
321
  audio.muted = true;
295
322
  audio.autoplay = true;
323
+ if (Browser().isSafariWebRTC()) {
324
+ audio.setAttribute("playsinline", "");
325
+ audio.setAttribute("webkit-playsinline", "");
326
+ this.setWebkitEventHandlers(audio);
327
+ } else {
328
+ this.setEventHandlers(audio);
329
+ }
296
330
  cell.appendChild(audio);
297
331
  audio.srcObject = stream;
298
332
  audio.onloadedmetadata = function (e) {
299
333
  audio.play().then(function() {
300
- audio.muted = false;
334
+ if (Browser().isSafariWebRTC() && Browser().isiOS()) {
335
+ console.warn("Audio track should be manually unmuted in iOS Safari");
336
+ } else {
337
+ audio.muted = false;
338
+ }
301
339
  });
302
340
  };
303
341
  },
@@ -319,22 +357,19 @@ const initRemoteDisplay = function(mainDiv, room, peerConnection) {
319
357
  return;
320
358
  }
321
359
  video = document.createElement("video");
360
+ video.controls = "controls";
322
361
  video.muted = true;
323
- if(Browser().isSafariWebRTC()) {
362
+ video.autoplay = true;
363
+ if (Browser().isSafariWebRTC()) {
324
364
  video.setAttribute("playsinline", "");
325
365
  video.setAttribute("webkit-playsinline", "");
366
+ this.setWebkitEventHandlers(video);
367
+ } else {
368
+ this.setEventHandlers(video);
326
369
  }
327
370
  streamDisplay.appendChild(video);
328
371
  video.srcObject = stream;
329
- video.onloadedmetadata = function (e) {
330
- video.play().then(function() {
331
- video.muted = false;
332
- });
333
- };
334
- video.addEventListener("resize", function (event) {
335
- streamNameDisplay.innerHTML = "Published by: " + name + "<br/>Current resolution: " + video.videoWidth + "x" + video.videoHeight;
336
- resizeVideo(event.target);
337
- });
372
+ this.setResizeHandler(video);
338
373
  },
339
374
  setTrackInfo: function(trackInfo) {
340
375
  if (trackInfo && trackInfo.quality) {
@@ -357,7 +392,7 @@ const initRemoteDisplay = function(mainDiv, room, peerConnection) {
357
392
  qualityDiv.style.color = "blue";
358
393
  room.changeQuality(trackInfo.id, trackInfo.quality[i]);
359
394
  });
360
- qualityDisplay.appendChild(qualityDiv);
395
+ qualitySwitchDisplay.appendChild(qualityDiv);
361
396
  }
362
397
  }
363
398
  },
@@ -378,6 +413,47 @@ const initRemoteDisplay = function(mainDiv, room, peerConnection) {
378
413
  hasVideo: function() {
379
414
  return video !== null || this.videoMid !== undefined;
380
415
  },
416
+ setResizeHandler: function(video) {
417
+ video.addEventListener("resize", function (event) {
418
+ if (displayOptions.publisher) {
419
+ publisherNameDisplay.innerHTML = "Published by: " + name;
420
+ }
421
+ if (displayOptions.quality) {
422
+ currentQualityDisplay.innerHTML = video.videoWidth + "x" + video.videoHeight;
423
+ }
424
+ resizeVideo(event.target);
425
+ });
426
+ },
427
+ setEventHandlers: function(video) {
428
+ // Ignore play/pause button
429
+ video.addEventListener("pause", function () {
430
+ console.log("Media paused by click, continue...");
431
+ video.play();
432
+ });
433
+ },
434
+ setWebkitEventHandlers: function(video) {
435
+ let needRestart = false;
436
+ let isFullscreen = false;
437
+ // Use webkitbeginfullscreen event to detect full screen mode in iOS Safari
438
+ video.addEventListener("webkitbeginfullscreen", function () {
439
+ isFullscreen = true;
440
+ });
441
+ video.addEventListener("pause", function () {
442
+ if (needRestart) {
443
+ console.log("Media paused after fullscreen, continue...");
444
+ video.play();
445
+ needRestart = false;
446
+ } else {
447
+ console.log("Media paused by click, continue...");
448
+ video.play();
449
+ }
450
+ });
451
+ video.addEventListener("webkitendfullscreen", function () {
452
+ video.play();
453
+ needRestart = true;
454
+ isFullscreen = false;
455
+ });
456
+ },
381
457
  audioMid: undefined,
382
458
  videoMid: undefined
383
459
  };
@@ -166,7 +166,11 @@ const onStopClick = function(state) {
166
166
 
167
167
  const playStreams = function(state) {
168
168
  //create remote display item to show remote streams
169
- remoteDisplay = initRemoteDisplay(document.getElementById("remoteVideo"), state.room, state.pc);
169
+ remoteDisplay = initRemoteDisplay({
170
+ div: document.getElementById("remoteVideo"),
171
+ room: state.room,
172
+ peerConnection: state.pc
173
+ });
170
174
  state.room.join(state.pc);
171
175
  }
172
176
 
@@ -300,7 +300,11 @@ const unPublishStreams = function(state) {
300
300
  const playStreams = function(state) {
301
301
  if (state.isConnected() && state.isActive()) {
302
302
  //create remote display item to show remote streams
303
- remoteDisplay = initRemoteDisplay(document.getElementById("remoteVideo"), state.room, state.pc);
303
+ remoteDisplay = initRemoteDisplay({
304
+ div: document.getElementById("remoteVideo"),
305
+ room: state.room,
306
+ peerConnection: state.pc
307
+ });
304
308
  state.room.join(state.pc);
305
309
  }
306
310
  $("#" + state.buttonId()).prop('disabled', false);
@@ -0,0 +1,27 @@
1
+ .fp-remoteVideo {
2
+ width: 802px;
3
+ text-align: center;
4
+ display: inline-block;
5
+ }
6
+
7
+ .fp-placeholder {
8
+ border: 1px double black;
9
+ width: 802px;
10
+ height: 402px;
11
+ text-align: center;
12
+ background: #c0c0c0;
13
+ display: inline-block;
14
+ }
15
+
16
+ video, object {
17
+ width: 100%;
18
+ height: 100%;
19
+ }
20
+
21
+ video:-webkit-full-screen {
22
+ border-radius: 1px;
23
+ }
24
+
25
+ video::-webkit-media-controls-play-button, video::-webkit-media-controls-pause-button {
26
+ display: none !important;
27
+ }
@@ -0,0 +1,49 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>WebRTC ABR Player</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet"
8
+ integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">
9
+ <!-- JavaScript Bundle with Popper -->
10
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js"
11
+ integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf"
12
+ crossorigin="anonymous"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
14
+ <link rel="stylesheet" href="player.css">
15
+ <script type="text/javascript" src="../sfu.js"></script>
16
+ <script type="text/javascript" src="../commons/js/util.js"></script>
17
+ <script type="text/javascript" src="../commons/js/config.js"></script>
18
+ <script type="text/javascript" src="../commons/js/display.js"></script>
19
+ <script type="text/javascript" src="player.js"></script>
20
+ </head>
21
+ <body onload="init()">
22
+ <div class="container" id="main">
23
+ <div class="col-sm-12">
24
+ <h2 class="text-center">WebRTC ABR Player</h2>
25
+
26
+ <div class="row col-sm-12 justify-content-center" style="margin-top: 10px">
27
+ <div id="remoteVideo" class="fp-remoteVideo text-center justify-content-center">
28
+ <div id="placeholder" class="fp-placeholder text-center justify-content-center"></div>
29
+ </div>
30
+ </div>
31
+ <div class="row col-sm-12 justify-content-center">
32
+ <div id="connectionForm" class="col-sm-6 text-center form-group">
33
+ <label for="url" class="control-label">Server url</label>
34
+ <input class="form-control" id="url" type="text">
35
+ <label for="streamName" class="control-label">Stream name</label>
36
+ <input class="form-control" id="streamName" type="text">
37
+ <div class="input-group-btn" style="margin-top: 10px">
38
+ <button id="playBtn" type="button" style="height: 30px;; width: auto;">Play</button>
39
+ </div>
40
+ <div class="text-center" style="margin-top: 20px">
41
+ <div id="playStatus"></div>
42
+ <div id="playErrorInfo"></div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </body>
49
+ </html>
@@ -0,0 +1,191 @@
1
+ const constants = SFU.constants;
2
+ const sfu = SFU;
3
+ const PRELOADER_URL="../commons/media/silence.mp3"
4
+
5
+ /**
6
+ * Current state object
7
+ */
8
+ const CurrentState = function() {
9
+ let state = {
10
+ pc: null,
11
+ session: null,
12
+ room: null,
13
+ remoteDisplay: null,
14
+ set: function(pc, session, room) {
15
+ state.pc = pc;
16
+ state.session = session;
17
+ state.room = room;
18
+ },
19
+ clear: function() {
20
+ state.room = null;
21
+ state.session = null;
22
+ state.pc = null;
23
+ },
24
+ setDisplay: function(display) {
25
+ state.remoteDisplay = display;
26
+ },
27
+ disposeDisplay: function() {
28
+ if (state.remoteDisplay) {
29
+ state.remoteDisplay.stop();
30
+ state.remoteDisplay = null;
31
+ }
32
+ }
33
+ };
34
+ return state;
35
+ }
36
+
37
+ /**
38
+ * load config and set default values
39
+ */
40
+ const init = function() {
41
+ $("#playBtn").prop('disabled', true);
42
+ $("#url").prop('disabled', true);
43
+ $("#streamName").prop('disabled', true);
44
+ onDisconnected(CurrentState());
45
+ $("#url").val(setURL());
46
+ }
47
+
48
+ /**
49
+ * Connect to server
50
+ */
51
+ const connect = function(state) {
52
+ // Create peer connection
53
+ let pc = new RTCPeerConnection();
54
+ // Create a config to connect to SFU room
55
+ const roomConfig = {
56
+ // Server websocket URL
57
+ url: $("#url").val(),
58
+ // Use stream name as room name to play ABR
59
+ roomName: $("#streamName").val(),
60
+ // Make a random participant name from stream name
61
+ nickname: "Player-" + $("#streamName").val() + "-" + createUUID(4),
62
+ // Set room pin
63
+ pin: 123456
64
+ }
65
+ // Clean state display items
66
+ setStatus("playStatus", "");
67
+ setStatus("playErrorInfo", "");
68
+ // Connect to the server (room should already exist)
69
+ const session = sfu.createRoom(roomConfig);
70
+ session.on(constants.SFU_EVENT.CONNECTED, function() {
71
+ state.set(pc, session, session.room());
72
+ onConnected(state);
73
+ setStatus("playStatus", "CONNECTING...", "black");
74
+ }).on(constants.SFU_EVENT.DISCONNECTED, function() {
75
+ state.clear();
76
+ onDisconnected(state);
77
+ setStatus("playStatus", "DISCONNECTED", "green");
78
+ }).on(constants.SFU_EVENT.FAILED, function(e) {
79
+ state.clear();
80
+ onDisconnected(state);
81
+ setStatus("playStatus", "FAILED", "red");
82
+ setStatus("playErrorInfo", e.status + " " + e.statusText, "red");
83
+ });
84
+ }
85
+
86
+ const onConnected = function(state) {
87
+ $("#playBtn").text("Stop").off('click').click(function () {
88
+ onStopClick(state);
89
+ }).prop('disabled', false);
90
+ $('#url').prop('disabled', true);
91
+ $("#streamName").prop('disabled', true);
92
+ // Add room event handling
93
+ state.room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, function(e) {
94
+ // If the room is empty, the stream is not published yet
95
+ if(!e.participants || e.participants.length === 0) {
96
+ setStatus("playErrorInfo", "ABR stream is not published", "red");
97
+ onStopClick(state);
98
+ }
99
+ else {
100
+ setStatus("playStatus", "ESTABLISHED", "green");
101
+ $("#placeholder").hide();
102
+ }
103
+ }).on(constants.SFU_ROOM_EVENT.FAILED, function(e) {
104
+ // Display error state
105
+ setStatus("playErrorInfo", e, "red");
106
+ }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
107
+ // Display the operation failed
108
+ setStatus("playErrorInfo", e.operation + " failed: " + e.error, "red");
109
+ }).on(constants.SFU_ROOM_EVENT.ENDED, function () {
110
+ // Publishing is stopped, dispose playback and close connection
111
+ setStatus("playErrorInfo", "ABR stream is stopped", "red");
112
+ onStopClick(state);
113
+ });
114
+ playStreams(state);
115
+ }
116
+
117
+ const onDisconnected = function(state) {
118
+ $("#placeholder").show();
119
+ $("#playBtn").text("Play").off('click').click(function () {
120
+ onStartClick(state);
121
+ }).prop('disabled', false);
122
+ $('#url').prop('disabled', false);
123
+ $("#streamName").prop('disabled', false);
124
+ }
125
+
126
+ const onStartClick = function(state) {
127
+ if (validateForm("connectionForm")) {
128
+ $("#playBtn").prop('disabled', true);
129
+ if (Browser().isSafariWebRTC()) {
130
+ playFirstSound(document.getElementById("main"), PRELOADER_URL).then(function () {
131
+ connect(state);
132
+ });
133
+ } else {
134
+ connect(state);
135
+ }
136
+ }
137
+ }
138
+
139
+ const onStopClick = function(state) {
140
+ $("#playBtn").prop('disabled', true);
141
+ stopStreams(state);
142
+ state.session.disconnect();
143
+ }
144
+
145
+ const playStreams = function(state) {
146
+ // Create remote display item to show remote streams
147
+ state.setDisplay(initRemoteDisplay({
148
+ div: document.getElementById("remoteVideo"),
149
+ room: state.room,
150
+ peerConnection: state.pc,
151
+ displayOptions: {
152
+ publisher: false,
153
+ quality: true
154
+ }
155
+ }));
156
+ state.room.join(state.pc);
157
+ }
158
+
159
+ const stopStreams = function(state) {
160
+ state.disposeDisplay();
161
+ }
162
+
163
+ const setStatus = function (status, text, color) {
164
+ color = color || "black";
165
+ const errField = document.getElementById(status);
166
+ errField.style.color = color;
167
+ errField.innerText = text;
168
+ }
169
+
170
+ const validateForm = function (formId) {
171
+ var valid = true;
172
+ $('#' + formId + ' :text').each(function () {
173
+ if (!$(this).val()) {
174
+ highlightInput($(this));
175
+ valid = false;
176
+ setStatus("playErrorInfo", "Fields cannot be empty", "red");
177
+ } else {
178
+ removeHighlight($(this));
179
+ setStatus("playErrorInfo", "");
180
+ }
181
+ });
182
+ return valid;
183
+
184
+ function highlightInput(input) {
185
+ input.closest('.form-group').addClass("has-error");
186
+ }
187
+
188
+ function removeHighlight(input) {
189
+ input.closest('.form-group').removeClass("has-error");
190
+ }
191
+ }