@flashphoner/sfusdk-examples 2.0.271 → 2.0.272
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
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
video, object {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.local-video-display {
|
|
7
|
+
border: 1px solid rgba(0, 0, 0, 0.8);
|
|
8
|
+
text-align: center;
|
|
9
|
+
width: 200px;
|
|
10
|
+
height: auto;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.display {
|
|
14
|
+
width: 100%;
|
|
15
|
+
height: 100%;
|
|
16
|
+
display: inline-block;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.display > video, object {
|
|
20
|
+
width: 100%;
|
|
21
|
+
height: 100%;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
video:-webkit-full-screen {
|
|
25
|
+
border-radius: 1px;
|
|
26
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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>Bitrate test</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="bitrate_test.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="bitrate_test.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">Bitrate test</h2>
|
|
25
|
+
|
|
26
|
+
<div class="row col-sm-12 justify-content-center">
|
|
27
|
+
<div id="connectionForm" class="col-sm-6 text-center">
|
|
28
|
+
<label for="url" class="control-label">Server url</label>
|
|
29
|
+
<input class="form-control" id="url" type="text">
|
|
30
|
+
<label for="roomName" class="control-label">Room name</label>
|
|
31
|
+
<input class="form-control" id="roomName" type="text">
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="row col-sm-12 justify-content-center" style="margin-top: 10px;">
|
|
35
|
+
<div class="col-sm-6 text-center">
|
|
36
|
+
<div class="text-center text-muted">Publisher</div>
|
|
37
|
+
<div id="publishForm" class="input-group col-sm-5" style="margin-top: 10px;">
|
|
38
|
+
<input class="form-control" id="publishName" type="text" style="height: 30px;"
|
|
39
|
+
placeholder="Publisher name">
|
|
40
|
+
<div class="input-group-btn">
|
|
41
|
+
<button id="publishBtn" type="button" style="height: 30px; width: auto;">Connect</button>
|
|
42
|
+
</div>
|
|
43
|
+
<button id="publishBitrateBtn" type="button" style="height: 30px; width: auto;">Check bitrate
|
|
44
|
+
</button>
|
|
45
|
+
<label for="publishBitrateStatus" class="control-label">Bitrate status</label>
|
|
46
|
+
<output id="publishBitrateStatus">
|
|
47
|
+
</div>
|
|
48
|
+
<div class="text-center" style="margin-top: 20px">
|
|
49
|
+
<div id="publishStatus"></div>
|
|
50
|
+
<div id="publishErrorInfo"></div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
const constants = SFU.constants;
|
|
2
|
+
const sfu = SFU;
|
|
3
|
+
let mainConfig;
|
|
4
|
+
let publishState;
|
|
5
|
+
|
|
6
|
+
const PUBLISH = "publish";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Default publishing config
|
|
10
|
+
*/
|
|
11
|
+
const defaultConfig = {
|
|
12
|
+
room: {
|
|
13
|
+
url: "ws://localhost:8080",
|
|
14
|
+
name: "ROOM1",
|
|
15
|
+
pin: "1234",
|
|
16
|
+
nickName: "User1",
|
|
17
|
+
failedProbesThreshold: 5,
|
|
18
|
+
pingInterval: 5000
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Current state object
|
|
24
|
+
*/
|
|
25
|
+
const CurrentState = function (prefix) {
|
|
26
|
+
let state = {
|
|
27
|
+
prefix: prefix,
|
|
28
|
+
pc: null,
|
|
29
|
+
session: null,
|
|
30
|
+
room: null,
|
|
31
|
+
display: null,
|
|
32
|
+
roomEnded: false,
|
|
33
|
+
starting: false,
|
|
34
|
+
bitrateController: null,
|
|
35
|
+
set: function (pc, session, room) {
|
|
36
|
+
state.pc = pc;
|
|
37
|
+
state.session = session;
|
|
38
|
+
state.room = room;
|
|
39
|
+
state.roomEnded = false;
|
|
40
|
+
},
|
|
41
|
+
clear: function () {
|
|
42
|
+
state.room = null;
|
|
43
|
+
state.session = null;
|
|
44
|
+
state.pc = null;
|
|
45
|
+
state.roomEnded = false;
|
|
46
|
+
},
|
|
47
|
+
setRoomEnded: function () {
|
|
48
|
+
state.roomEnded = true;
|
|
49
|
+
},
|
|
50
|
+
buttonId: function () {
|
|
51
|
+
return state.prefix + "Btn";
|
|
52
|
+
},
|
|
53
|
+
buttonText: function () {
|
|
54
|
+
return "Connect";
|
|
55
|
+
},
|
|
56
|
+
inputId: function () {
|
|
57
|
+
return state.prefix + "Name";
|
|
58
|
+
},
|
|
59
|
+
statusId: function () {
|
|
60
|
+
return state.prefix + "Status";
|
|
61
|
+
},
|
|
62
|
+
formId: function () {
|
|
63
|
+
return state.prefix + "Form";
|
|
64
|
+
},
|
|
65
|
+
errInfoId: function () {
|
|
66
|
+
return state.prefix + "ErrorInfo";
|
|
67
|
+
},
|
|
68
|
+
bitrateButtonId: function () {
|
|
69
|
+
return state.prefix + "BitrateBtn";
|
|
70
|
+
},
|
|
71
|
+
bitrateStatusId: function () {
|
|
72
|
+
return state.prefix + "BitrateStatus";
|
|
73
|
+
},
|
|
74
|
+
getBitrateController: function () {
|
|
75
|
+
return this.bitrateController;
|
|
76
|
+
},
|
|
77
|
+
setBitrateController: function (controller) {
|
|
78
|
+
this.bitrateController = controller;
|
|
79
|
+
},
|
|
80
|
+
is: function (value) {
|
|
81
|
+
return (prefix === value);
|
|
82
|
+
},
|
|
83
|
+
isActive: function () {
|
|
84
|
+
return (state.room && !state.roomEnded && state.pc);
|
|
85
|
+
},
|
|
86
|
+
isConnected: function () {
|
|
87
|
+
return (state.session && state.session.state() === constants.SFU_STATE.CONNECTED);
|
|
88
|
+
},
|
|
89
|
+
isRoomEnded: function () {
|
|
90
|
+
return state.roomEnded;
|
|
91
|
+
},
|
|
92
|
+
setStarting: function (value) {
|
|
93
|
+
state.starting = value;
|
|
94
|
+
},
|
|
95
|
+
isStarting: function () {
|
|
96
|
+
return state.starting;
|
|
97
|
+
},
|
|
98
|
+
setDisplay: function (display) {
|
|
99
|
+
state.display = display;
|
|
100
|
+
},
|
|
101
|
+
disposeDisplay: function () {
|
|
102
|
+
if (state.display) {
|
|
103
|
+
state.display.stop();
|
|
104
|
+
state.display = null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
return state;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* load config and set default values
|
|
113
|
+
*/
|
|
114
|
+
const init = function () {
|
|
115
|
+
$("#publishBtn").prop('disabled', true);
|
|
116
|
+
$("#url").prop('disabled', true);
|
|
117
|
+
$("#roomName").prop('disabled', true);
|
|
118
|
+
$("#publishName").prop('disabled', true);
|
|
119
|
+
$("#BitrateBtn").prop('disabled', true);
|
|
120
|
+
publishState = CurrentState(PUBLISH);
|
|
121
|
+
mainConfig = defaultConfig;
|
|
122
|
+
onDisconnected(publishState);
|
|
123
|
+
$("#url").val(mainConfig.room.url);
|
|
124
|
+
$("#roomName").val("ROOM1-" + createUUID(4));
|
|
125
|
+
$("#publishName").val("Publisher1-" + createUUID(4));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* connect to server
|
|
130
|
+
*/
|
|
131
|
+
const connect = async function (state) {
|
|
132
|
+
//create peer connection
|
|
133
|
+
const pc = new RTCPeerConnection();
|
|
134
|
+
//get config object for room creation
|
|
135
|
+
const roomConfig = getRoomConfig(mainConfig);
|
|
136
|
+
roomConfig.url = $("#url").val();
|
|
137
|
+
roomConfig.roomName = $("#roomName").val();
|
|
138
|
+
roomConfig.nickname = createUUID(5);
|
|
139
|
+
// clean state display items
|
|
140
|
+
setStatus(state.statusId(), "");
|
|
141
|
+
setStatus(state.errInfoId(), "");
|
|
142
|
+
// connect to server and create a room if not
|
|
143
|
+
try {
|
|
144
|
+
const session = await sfu.createRoom(roomConfig);
|
|
145
|
+
// Set up session ending events
|
|
146
|
+
session.on(constants.SFU_EVENT.DISCONNECTED, function () {
|
|
147
|
+
onStopClick(state);
|
|
148
|
+
onDisconnected(state);
|
|
149
|
+
setStatus(state.statusId(), "DISCONNECTED", "green");
|
|
150
|
+
}).on(constants.SFU_EVENT.FAILED, function (e) {
|
|
151
|
+
onStopClick(state);
|
|
152
|
+
onDisconnected(state);
|
|
153
|
+
setStatus(state.statusId(), "FAILED", "red");
|
|
154
|
+
if (e.status && e.statusText) {
|
|
155
|
+
setStatus(state.errInfoId(), e.status + " " + e.statusText, "red");
|
|
156
|
+
} else if (e.type && e.info) {
|
|
157
|
+
setStatus(state.errInfoId(), e.type + ": " + e.info, "red");
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
// Connected successfully
|
|
161
|
+
onConnected(state, pc, session);
|
|
162
|
+
setStatus(state.statusId(), "ESTABLISHED", "green");
|
|
163
|
+
} catch (e) {
|
|
164
|
+
onDisconnected(state);
|
|
165
|
+
setStatus(state.statusId(), "FAILED", "red");
|
|
166
|
+
setStatus(state.errInfoId(), e, "red");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const onConnected = function (state, pc, session) {
|
|
171
|
+
state.set(pc, session, session.room());
|
|
172
|
+
$("#" + state.buttonId()).text("Disconnect").off('click').click(function () {
|
|
173
|
+
onStopClick(state);
|
|
174
|
+
});
|
|
175
|
+
$("#" + state.bitrateButtonId()).off('click').click(function () {
|
|
176
|
+
onStartBitrateClick(state);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
$('#url').prop('disabled', true);
|
|
180
|
+
$("#roomName").prop('disabled', true);
|
|
181
|
+
$("#" + state.inputId()).prop('disabled', true);
|
|
182
|
+
// Add errors displaying
|
|
183
|
+
state.room.on(constants.SFU_ROOM_EVENT.FAILED, function (e) {
|
|
184
|
+
setStatus(state.errInfoId(), e, "red");
|
|
185
|
+
state.setRoomEnded();
|
|
186
|
+
onStopClick(state);
|
|
187
|
+
}).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
|
|
188
|
+
onOperationFailed(state, e);
|
|
189
|
+
}).on(constants.SFU_ROOM_EVENT.ENDED, function () {
|
|
190
|
+
setStatus(state.errInfoId(), "Room " + state.room.name() + " has ended", "red");
|
|
191
|
+
state.setRoomEnded();
|
|
192
|
+
onStopClick(state);
|
|
193
|
+
}).on(constants.SFU_ROOM_EVENT.DROPPED, function () {
|
|
194
|
+
setStatus(state.errInfoId(), "Dropped from the room " + state.room.name() + " due to network issues", "red");
|
|
195
|
+
state.setRoomEnded();
|
|
196
|
+
onStopClick(state);
|
|
197
|
+
});
|
|
198
|
+
startStreaming(state);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const onDisconnected = function (state) {
|
|
202
|
+
state.clear();
|
|
203
|
+
$("#" + state.buttonId()).text(state.buttonText()).off('click').click(function () {
|
|
204
|
+
onStartClick(state);
|
|
205
|
+
}).prop('disabled', false);
|
|
206
|
+
$('#url').prop('disabled', false);
|
|
207
|
+
$("#roomName").prop('disabled', false);
|
|
208
|
+
$("#" + state.inputId()).prop('disabled', false);
|
|
209
|
+
$("#" + state.bitrateButtonId()).prop('disabled', true);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const onStartClick = function (state) {
|
|
213
|
+
if (validateForm("connectionForm", state.errInfoId())
|
|
214
|
+
&& validateForm(state.formId(), state.errInfoId())) {
|
|
215
|
+
state.setStarting(true);
|
|
216
|
+
if (!state.is(PUBLISH) && Browser().isSafariWebRTC()) {
|
|
217
|
+
playFirstSound(document.getElementById("main"), PRELOADER_URL).then(function () {
|
|
218
|
+
connect(state);
|
|
219
|
+
});
|
|
220
|
+
} else {
|
|
221
|
+
connect(state);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const onStartBitrateClick = async function (state) {
|
|
227
|
+
if (state.is(PUBLISH) && validateForm("connectionForm", state.errInfoId())
|
|
228
|
+
&& validateForm(state.formId(), state.errInfoId()) &&
|
|
229
|
+
state.room) {
|
|
230
|
+
const statusSelector = $("#" + state.bitrateStatusId());
|
|
231
|
+
statusSelector.attr("style", "display:inline-block;margin-left: 10px");
|
|
232
|
+
try {
|
|
233
|
+
const bitrateTest = state.room.getBitrateTest();
|
|
234
|
+
state.setBitrateController(bitrateTest);
|
|
235
|
+
bitrateTest.setListener({
|
|
236
|
+
onStatusUpdate(status) {
|
|
237
|
+
statusSelector.text(" = " + status);
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
bitrateTest.test(30_000).then((bitrate) => {
|
|
241
|
+
statusSelector.text("Test ended, last bitrate - " + bitrate);
|
|
242
|
+
state.setBitrateController(null);
|
|
243
|
+
$("#" + state.bitrateButtonId()).text("Start test").off('click').click(function () {
|
|
244
|
+
onStartBitrateClick(state);
|
|
245
|
+
});
|
|
246
|
+
})
|
|
247
|
+
$("#" + state.bitrateButtonId()).text("Stop test").off('click').click(function () {
|
|
248
|
+
onStopBitrateClick(state);
|
|
249
|
+
});
|
|
250
|
+
} catch (e) {
|
|
251
|
+
setStatus(state.errInfoId(), e);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const onStopBitrateClick = function (state) {
|
|
258
|
+
if (state.is(PUBLISH)) {
|
|
259
|
+
const controller = state.getBitrateController();
|
|
260
|
+
if (controller) {
|
|
261
|
+
controller.stop();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const onOperationFailed = function (state, event) {
|
|
267
|
+
if (event.operation && event.error) {
|
|
268
|
+
setStatus(state.errInfoId(), event.operation + " failed: " + event.error, "red");
|
|
269
|
+
} else {
|
|
270
|
+
setStatus(state.errInfoId(), event, "red");
|
|
271
|
+
}
|
|
272
|
+
state.setRoomEnded();
|
|
273
|
+
onStopClick(state);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const onStopClick = async function (state) {
|
|
277
|
+
state.setStarting(false);
|
|
278
|
+
disposeStateDisplay(state);
|
|
279
|
+
if (state.isConnected()) {
|
|
280
|
+
$("#" + state.buttonId()).prop('disabled', true);
|
|
281
|
+
onStopBitrateClick(state);
|
|
282
|
+
await state.session.disconnect();
|
|
283
|
+
onDisconnected(state);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const startStreaming = async function (state) {
|
|
288
|
+
await publishStreams(state);
|
|
289
|
+
state.setStarting(false);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const publishStreams = async function (state) {
|
|
293
|
+
if (state.isConnected()) {
|
|
294
|
+
try {
|
|
295
|
+
if (state.isConnected() && state.isActive()) {
|
|
296
|
+
await state.room.join(state.pc, null, null);
|
|
297
|
+
$("#" + state.bitrateButtonId()).prop('disabled', false);
|
|
298
|
+
}
|
|
299
|
+
} catch (e) {
|
|
300
|
+
if (e.type === constants.SFU_ROOM_EVENT.OPERATION_FAILED) {
|
|
301
|
+
onOperationFailed(state, e);
|
|
302
|
+
} else {
|
|
303
|
+
console.error("Failed to capture streams: " + e);
|
|
304
|
+
setStatus(state.errInfoId(), e.name, "red");
|
|
305
|
+
onStopClick(state);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
const disposeStateDisplay = function (state) {
|
|
313
|
+
state.disposeDisplay();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const setStatus = function (status, text, color) {
|
|
317
|
+
const field = document.getElementById(status);
|
|
318
|
+
if (color) {
|
|
319
|
+
field.style.color = color;
|
|
320
|
+
}
|
|
321
|
+
field.innerText = text;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const validateForm = function (formId, errorInfoId) {
|
|
325
|
+
let valid = true;
|
|
326
|
+
// Validate empty fields
|
|
327
|
+
$('#' + formId + ' :text').each(function () {
|
|
328
|
+
if (!$(this).val()) {
|
|
329
|
+
highlightInput($(this));
|
|
330
|
+
valid = false;
|
|
331
|
+
setStatus(errorInfoId, "Fields cannot be empty", "red");
|
|
332
|
+
} else {
|
|
333
|
+
removeHighlight($(this));
|
|
334
|
+
setStatus(errorInfoId, "");
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
return valid;
|
|
338
|
+
|
|
339
|
+
function highlightInput(input) {
|
|
340
|
+
input.closest('.input-group').addClass("has-error");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function removeHighlight(input) {
|
|
344
|
+
input.closest('.input-group').removeClass("has-error");
|
|
345
|
+
}
|
|
346
|
+
}
|