@flashphoner/sfusdk 1.0.1-35 → 1.0.40
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/Gruntfile.js +13 -0
- package/README.md +1 -1
- package/docTemplate/README.md +1 -1
- package/package.json +13 -5
- package/src/examples/commons/js/config.js +81 -0
- package/src/examples/commons/js/display.js +484 -0
- package/src/examples/commons/js/util.js +202 -0
- package/src/examples/commons/media/silence.mp3 +0 -0
- package/src/examples/player/config.json +8 -0
- package/src/examples/player/player.css +19 -0
- package/src/examples/player/player.html +54 -0
- package/src/examples/player/player.js +206 -0
- package/src/examples/two-way-streaming/config.json +34 -0
- package/src/examples/two-way-streaming/two-way-streaming.css +26 -0
- package/src/examples/two-way-streaming/two-way-streaming.html +72 -0
- package/src/examples/two-way-streaming/two-way-streaming.js +346 -0
- package/src/sdk/constants.js +30 -2
- package/src/sdk/messaging.js +22 -11
- package/src/sdk/promise.js +13 -3
- package/src/sdk/sfu-extended.js +149 -19
- package/src/tests/sdk/sfu-extended.test.js +151 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const stripCodecs = function(sdp, codecs) {
|
|
2
|
+
if (!codecs.length) return sdp;
|
|
3
|
+
var sdpArray = sdp.split("\n");
|
|
4
|
+
var codecsArray = codecs.split(",");
|
|
5
|
+
|
|
6
|
+
//search and delete codecs line
|
|
7
|
+
var pt = [];
|
|
8
|
+
var i;
|
|
9
|
+
for (var p = 0; p < codecsArray.length; p++) {
|
|
10
|
+
console.log("Searching for codec " + codecsArray[p]);
|
|
11
|
+
for (i = 0; i < sdpArray.length; i++) {
|
|
12
|
+
if (sdpArray[i].search(new RegExp(codecsArray[p],'i')) !== -1 && sdpArray[i].indexOf("a=rtpmap") === 0) {
|
|
13
|
+
console.log(codecsArray[p] + " detected");
|
|
14
|
+
pt.push(sdpArray[i].match(/[0-9]+/)[0]);
|
|
15
|
+
sdpArray[i] = "";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (pt.length) {
|
|
20
|
+
//searching for fmtp
|
|
21
|
+
for (p = 0; p < pt.length; p++) {
|
|
22
|
+
for (i = 0; i < sdpArray.length; i++) {
|
|
23
|
+
if (sdpArray[i].search("a=fmtp:" + pt[p]) !== -1 || sdpArray[i].search("a=rtcp-fb:" + pt[p]) !== -1) {
|
|
24
|
+
sdpArray[i] = "";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//delete entries from m= line
|
|
30
|
+
for (i = 0; i < sdpArray.length; i++) {
|
|
31
|
+
if (sdpArray[i].search("m=audio") !== -1 || sdpArray[i].search("m=video") !== -1) {
|
|
32
|
+
var mLineSplitted = sdpArray[i].split(" ");
|
|
33
|
+
var newMLine = "";
|
|
34
|
+
for (var m = 0; m < mLineSplitted.length; m++) {
|
|
35
|
+
if (pt.indexOf(mLineSplitted[m].trim()) === -1 || m <= 2) {
|
|
36
|
+
newMLine += mLineSplitted[m];
|
|
37
|
+
if (m < mLineSplitted.length - 1) {
|
|
38
|
+
newMLine = newMLine + " ";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
sdpArray[i] = newMLine;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//normalize sdp after modifications
|
|
48
|
+
var result = "";
|
|
49
|
+
for (i = 0; i < sdpArray.length; i++) {
|
|
50
|
+
if (sdpArray[i] !== "") {
|
|
51
|
+
result += sdpArray[i] + "\n";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const stripVideoCodecsExcept = function (sdp, codec) {
|
|
58
|
+
let actualStripCodec = "rtx";
|
|
59
|
+
if (codec === "VP8") {
|
|
60
|
+
actualStripCodec += ",H264";
|
|
61
|
+
} else if (codec === "H264") {
|
|
62
|
+
actualStripCodec += ",VP8";
|
|
63
|
+
} else {
|
|
64
|
+
return sdp;
|
|
65
|
+
}
|
|
66
|
+
return stripCodecs(sdp, actualStripCodec);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//Set WCS URL
|
|
70
|
+
const setURL = function () {
|
|
71
|
+
var proto;
|
|
72
|
+
var url;
|
|
73
|
+
var port;
|
|
74
|
+
if (window.location.protocol == "http:") {
|
|
75
|
+
proto = "ws://";
|
|
76
|
+
port = "8080";
|
|
77
|
+
} else {
|
|
78
|
+
proto = "wss://";
|
|
79
|
+
port = "8443";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
url = proto + window.location.hostname + ":" + port;
|
|
83
|
+
return url;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Get URL parameter
|
|
87
|
+
const getUrlParam = function (name) {
|
|
88
|
+
var url = window.location.href;
|
|
89
|
+
name = name.replace(/[\[\]]/g, "\\$&");
|
|
90
|
+
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
|
91
|
+
results = regex.exec(url);
|
|
92
|
+
if (!results) return null;
|
|
93
|
+
if (!results[2]) return '';
|
|
94
|
+
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Generate simple uuid
|
|
98
|
+
const createUUID = function (length) {
|
|
99
|
+
var s = [];
|
|
100
|
+
var hexDigits = "0123456789abcdef";
|
|
101
|
+
for (var i = 0; i < 36; i++) {
|
|
102
|
+
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
|
|
103
|
+
}
|
|
104
|
+
s[14] = "4";
|
|
105
|
+
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
|
|
106
|
+
s[8] = s[13] = s[18] = s[23] = "-";
|
|
107
|
+
|
|
108
|
+
var uuid = s.join("");
|
|
109
|
+
|
|
110
|
+
return uuid.substring(0, length);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const Browser = function() {
|
|
114
|
+
const isIE = function () {
|
|
115
|
+
return /*@cc_on!@*/false || !!document.documentMode;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const isFirefox = function () {
|
|
119
|
+
return typeof InstallTrigger !== 'undefined';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const isChrome = function () {
|
|
123
|
+
return !!window.chrome && /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor) && !/OPR/.test(navigator.userAgent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const isEdge = function () {
|
|
127
|
+
return !this.isIE() && !!window.StyleMedia;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const isOpera = function () {
|
|
131
|
+
return (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const isiOS = function () {
|
|
135
|
+
return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const isSafari = function () {
|
|
139
|
+
let userAgent = navigator.userAgent.toLowerCase();
|
|
140
|
+
return /(safari|applewebkit)/i.test(userAgent) && !userAgent.includes("chrome") && !userAgent.includes("android");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const isAndroid = function () {
|
|
144
|
+
return navigator.userAgent.toLowerCase().indexOf("android") > -1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const isSafariWebRTC = function () {
|
|
148
|
+
return navigator.mediaDevices && this.isSafari();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const isSamsungBrowser = function () {
|
|
152
|
+
return /SamsungBrowser/i.test(navigator.userAgent);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const isAndroidFirefox = function () {
|
|
156
|
+
return this.isAndroid() && /Firefox/i.test(navigator.userAgent);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const isMobile = function () {
|
|
160
|
+
return this.isAndroid() || this.isiOS();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
isIE: isIE,
|
|
165
|
+
isFirefox: isFirefox,
|
|
166
|
+
isChrome: isChrome,
|
|
167
|
+
isEdge: isEdge,
|
|
168
|
+
isOpera: isOpera,
|
|
169
|
+
isSafari: isSafari,
|
|
170
|
+
isSafariWebRTC: isSafariWebRTC,
|
|
171
|
+
isSamsungBrowser: isSamsungBrowser,
|
|
172
|
+
isAndroid: isAndroid,
|
|
173
|
+
isiOS: isiOS,
|
|
174
|
+
isAndroidFirefox: isAndroidFirefox,
|
|
175
|
+
isMobile: isMobile
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
const playFirstSound = function (parent, preloader) {
|
|
181
|
+
return new Promise(function (resolve, reject) {
|
|
182
|
+
let audio = document.createElement("audio");
|
|
183
|
+
audio.controls = "controls";
|
|
184
|
+
audio.muted = true;
|
|
185
|
+
audio.hidden = true;
|
|
186
|
+
audio.preload = "auto";
|
|
187
|
+
audio.type="audio/mpeg";
|
|
188
|
+
if (preloader) {
|
|
189
|
+
audio.src = preloader;
|
|
190
|
+
parent.appendChild(audio);
|
|
191
|
+
audio.play().then(function() {
|
|
192
|
+
audio.remove();
|
|
193
|
+
resolve();
|
|
194
|
+
}).catch(function (e) {
|
|
195
|
+
console.error("Can't play preloader: " + e);
|
|
196
|
+
reject();
|
|
197
|
+
});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
resolve();
|
|
201
|
+
});
|
|
202
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
video, object {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: 100%;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.display {
|
|
7
|
+
width: 100%;
|
|
8
|
+
height: 100%;
|
|
9
|
+
display: inline-block;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.display > video, object {
|
|
13
|
+
width: 100%;
|
|
14
|
+
height: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
video:-webkit-full-screen {
|
|
18
|
+
border-radius: 1px;
|
|
19
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
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>SFU 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">SFU Player</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">Player</div>
|
|
37
|
+
<div id="playForm" class="input-group col-sm-5" style="margin-top: 10px;">
|
|
38
|
+
<input class="form-control" id="playName" type="text" style="height: 30px;" placeholder="Player name">
|
|
39
|
+
<div class="input-group-btn">
|
|
40
|
+
<button id="playBtn" type="button" style="height: 30px;; width: auto;">Play</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="text-center" style="margin-top: 20px">
|
|
44
|
+
<div id="playStatus"></div>
|
|
45
|
+
<div id="playErrorInfo"></div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="row col-sm-12 justify-content-center" style="margin-top: 10px">
|
|
50
|
+
<div id="remoteVideo" class="col-sm-6 justify-content-center"></div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
const constants = SFU.constants;
|
|
2
|
+
const sfu = SFU;
|
|
3
|
+
let mainConfig;
|
|
4
|
+
let remoteDisplay;
|
|
5
|
+
let playState;
|
|
6
|
+
const PLAY = "play";
|
|
7
|
+
const STOP = "stop";
|
|
8
|
+
const PRELOADER_URL="../commons/media/silence.mp3"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default publishing config
|
|
12
|
+
*/
|
|
13
|
+
const defaultConfig = {
|
|
14
|
+
room: {
|
|
15
|
+
url: "ws://127.0.0.1:8080",
|
|
16
|
+
name: "ROOM1",
|
|
17
|
+
pin: "1234",
|
|
18
|
+
nickName: "User1"
|
|
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
|
+
set: function(pc, session, room) {
|
|
32
|
+
state.pc = pc;
|
|
33
|
+
state.session = session;
|
|
34
|
+
state.room = room;
|
|
35
|
+
},
|
|
36
|
+
clear: function() {
|
|
37
|
+
state.room = null;
|
|
38
|
+
state.session = null;
|
|
39
|
+
state.pc = null;
|
|
40
|
+
},
|
|
41
|
+
buttonId: function() {
|
|
42
|
+
return state.prefix + "Btn";
|
|
43
|
+
},
|
|
44
|
+
buttonText: function() {
|
|
45
|
+
return (state.prefix.charAt(0).toUpperCase() + state.prefix.slice(1));
|
|
46
|
+
},
|
|
47
|
+
inputId: function() {
|
|
48
|
+
return state.prefix + "Name";
|
|
49
|
+
},
|
|
50
|
+
statusId: function() {
|
|
51
|
+
return state.prefix + "Status";
|
|
52
|
+
},
|
|
53
|
+
formId: function() {
|
|
54
|
+
return state.prefix + "Form";
|
|
55
|
+
},
|
|
56
|
+
errInfoId: function() {
|
|
57
|
+
return state.prefix + "ErrorInfo";
|
|
58
|
+
},
|
|
59
|
+
is: function(value) {
|
|
60
|
+
return (prefix === value);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
return state;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* load config and set default values
|
|
68
|
+
*/
|
|
69
|
+
const init = function() {
|
|
70
|
+
let configName = getUrlParam("config") || "./config.json";
|
|
71
|
+
$("#playBtn").prop('disabled', true);
|
|
72
|
+
$("#url").prop('disabled', true);
|
|
73
|
+
$("#roomName").prop('disabled', true);
|
|
74
|
+
$("#playName").prop('disabled', true);
|
|
75
|
+
playState = CurrentState(PLAY);
|
|
76
|
+
$.getJSON(configName, function(cfg){
|
|
77
|
+
mainConfig = cfg;
|
|
78
|
+
onDisconnected(playState);
|
|
79
|
+
}).fail(function(e){
|
|
80
|
+
//use default config
|
|
81
|
+
console.error("Error reading configuration file " + configName + ": " + e.status + " " + e.statusText)
|
|
82
|
+
console.log("Default config will be used");
|
|
83
|
+
mainConfig = defaultConfig;
|
|
84
|
+
onDisconnected(playState);
|
|
85
|
+
});
|
|
86
|
+
$("#url").val(setURL());
|
|
87
|
+
$("#roomName").val("ROOM1-"+createUUID(4));
|
|
88
|
+
$("#playName").val("Player1-"+createUUID(4));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* connect to server
|
|
93
|
+
*/
|
|
94
|
+
const connect = function(state) {
|
|
95
|
+
//create peer connection
|
|
96
|
+
pc = new RTCPeerConnection();
|
|
97
|
+
//get config object for room creation
|
|
98
|
+
const roomConfig = getRoomConfig(mainConfig);
|
|
99
|
+
roomConfig.pc = pc;
|
|
100
|
+
roomConfig.url = $("#url").val();
|
|
101
|
+
roomConfig.roomName = $("#roomName").val();
|
|
102
|
+
roomConfig.nickname = $("#" + state.inputId()).val();
|
|
103
|
+
// connect to server and create a room if not
|
|
104
|
+
const session = sfu.createRoom(roomConfig);
|
|
105
|
+
session.on(constants.SFU_EVENT.CONNECTED, function(room) {
|
|
106
|
+
state.set(pc, session, room);
|
|
107
|
+
onConnected(state);
|
|
108
|
+
setStatus(state.statusId(), "ESTABLISHED", "green");
|
|
109
|
+
}).on(constants.SFU_EVENT.DISCONNECTED, function() {
|
|
110
|
+
state.clear();
|
|
111
|
+
onDisconnected(state);
|
|
112
|
+
setStatus(state.statusId(), "DISCONNECTED", "green");
|
|
113
|
+
}).on(constants.SFU_EVENT.FAILED, function(e) {
|
|
114
|
+
state.clear();
|
|
115
|
+
onDisconnected(state);
|
|
116
|
+
setStatus(state.statusId(), "FAILED", "red");
|
|
117
|
+
setStatus(state.errInfoId(), e.status + " " + e.statusText, "red");
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const onConnected = function(state) {
|
|
122
|
+
$("#" + state.buttonId()).text("Stop").off('click').click(function () {
|
|
123
|
+
onStopClick(state);
|
|
124
|
+
}).prop('disabled', false);
|
|
125
|
+
$('#url').prop('disabled', true);
|
|
126
|
+
$("#roomName").prop('disabled', true);
|
|
127
|
+
$("#" + state.inputId()).prop('disabled', true);
|
|
128
|
+
// Add errors displaying
|
|
129
|
+
state.room.on(constants.SFU_ROOM_EVENT.FAILED, function(e) {
|
|
130
|
+
setStatus(state.errInfoId(), e, "red");
|
|
131
|
+
}).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
|
|
132
|
+
setStatus(state.errInfoId(), e.operation + " failed: " + e.error, "red");
|
|
133
|
+
});
|
|
134
|
+
playStreams(state);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const onDisconnected = function(state) {
|
|
138
|
+
$("#" + state.buttonId()).text(state.buttonText()).off('click').click(function () {
|
|
139
|
+
onStartClick(state);
|
|
140
|
+
}).prop('disabled', false);
|
|
141
|
+
$('#url').prop('disabled', false);
|
|
142
|
+
$("#roomName").prop('disabled', false);
|
|
143
|
+
$("#" + state.inputId()).prop('disabled', false);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const onStartClick = function(state) {
|
|
147
|
+
if (validateForm("connectionForm") && validateForm(state.formId())) {
|
|
148
|
+
$("#" + state.buttonId()).prop('disabled', true);
|
|
149
|
+
if (state.is(PLAY) && Browser().isSafariWebRTC()) {
|
|
150
|
+
playFirstSound(document.getElementById("main"), PRELOADER_URL).then(function () {
|
|
151
|
+
connect(state);
|
|
152
|
+
});
|
|
153
|
+
} else {
|
|
154
|
+
connect(state);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const onStopClick = function(state) {
|
|
160
|
+
$("#" + state.buttonId()).prop('disabled', true);
|
|
161
|
+
stopStreams(state);
|
|
162
|
+
state.session.disconnect();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const playStreams = function(state) {
|
|
166
|
+
//create remote display item to show remote streams
|
|
167
|
+
remoteDisplay = initRemoteDisplay(document.getElementById("remoteVideo"), state.room, state.pc);
|
|
168
|
+
state.room.join();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const stopStreams = function(state) {
|
|
172
|
+
if (remoteDisplay) {
|
|
173
|
+
remoteDisplay.stop();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const setStatus = function (status, text, color) {
|
|
178
|
+
const errField = document.getElementById(status);
|
|
179
|
+
errField.style.color = color;
|
|
180
|
+
errField.innerText = text;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const validateForm = function (formId) {
|
|
184
|
+
var valid = true;
|
|
185
|
+
$('#' + formId + ' :text').each(function () {
|
|
186
|
+
if (!$(this).val()) {
|
|
187
|
+
highlightInput($(this));
|
|
188
|
+
valid = false;
|
|
189
|
+
} else {
|
|
190
|
+
removeHighlight($(this));
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return valid;
|
|
194
|
+
|
|
195
|
+
function highlightInput(input) {
|
|
196
|
+
input.closest('.input-group').addClass("has-error");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function removeHighlight(input) {
|
|
200
|
+
input.closest('.input-group').removeClass("has-error");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const buttonText = function (string) {
|
|
205
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
206
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"room": {
|
|
3
|
+
"url": "ws://127.0.0.1:8080",
|
|
4
|
+
"name": "ROOM1",
|
|
5
|
+
"pin": "1234",
|
|
6
|
+
"nickName": "User1"
|
|
7
|
+
},
|
|
8
|
+
"media": {
|
|
9
|
+
"audio": {
|
|
10
|
+
"tracks": [{
|
|
11
|
+
"source": "mic",
|
|
12
|
+
"channels": 2
|
|
13
|
+
}]
|
|
14
|
+
},
|
|
15
|
+
"video": {
|
|
16
|
+
"tracks": [
|
|
17
|
+
{
|
|
18
|
+
"source": "camera",
|
|
19
|
+
"width": 1280,
|
|
20
|
+
"height": 720,
|
|
21
|
+
"codec": "H264",
|
|
22
|
+
"constraints": {
|
|
23
|
+
"frameRate": 25
|
|
24
|
+
},
|
|
25
|
+
"encodings": [
|
|
26
|
+
{ "rid": "720p", "active": true, "maxBitrate": 900000 },
|
|
27
|
+
{ "rid": "360p", "active": true, "maxBitrate": 500000, "scaleResolutionDownBy": 2 },
|
|
28
|
+
{ "rid": "180p", "active": true, "maxBitrate": 200000, "scaleResolutionDownBy": 4 }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -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,72 @@
|
|
|
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>SFU Two Way Streaming</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="two-way-streaming.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="two-way-streaming.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">SFU Two-way Streaming</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;" placeholder="Publisher name">
|
|
39
|
+
<div class="input-group-btn">
|
|
40
|
+
<button id="publishBtn" type="button" style="height: 30px; width: auto;">Publish</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="text-center" style="margin-top: 20px">
|
|
44
|
+
<div id="publishStatus"></div>
|
|
45
|
+
<div id="publishErrorInfo"></div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="row col-sm-12 justify-content-center" style="margin-top: 10px">
|
|
50
|
+
<div id="localVideo" class="col-sm-6 justify-content-center"></div>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="row col-sm-12 justify-content-center" style="margin-top: 10px;">
|
|
53
|
+
<div class="col-sm-6 text-center">
|
|
54
|
+
<div class="text-center text-muted">Player</div>
|
|
55
|
+
<div id="playForm" class="input-group col-sm-5" style="margin-top: 10px;">
|
|
56
|
+
<input class="form-control" id="playName" type="text" style="height: 30px;" placeholder="Player name">
|
|
57
|
+
<div class="input-group-btn">
|
|
58
|
+
<button id="playBtn" type="button" style="height: 30px;; width: auto;">Play</button>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="text-center" style="margin-top: 20px">
|
|
62
|
+
<div id="playStatus"></div>
|
|
63
|
+
<div id="playErrorInfo"></div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="row col-sm-12 justify-content-center" style="margin-top: 10px">
|
|
68
|
+
<div id="remoteVideo" class="col-sm-6 justify-content-center"></div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</body>
|
|
72
|
+
</html>
|