ovenlivekit 1.1.0 → 1.3.0
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/README.md +60 -4
- package/dist/OvenLiveKit.js +3 -3
- package/dist/OvenLiveKit.min.js +1 -1
- package/dist/OvenLiveKit.min.js.map +1 -1
- package/package.json +5 -4
- package/src/OvenLiveKit.js +824 -543
- package/.github/FUNDING.yml +0 -2
- package/assets/05_OvenSpace_230214.png +0 -0
- package/index.html +0 -41
- package/webpack.dev.js +0 -17
- package/webpack.prod.js +0 -18
package/src/OvenLiveKit.js
CHANGED
|
@@ -1,868 +1,1149 @@
|
|
|
1
1
|
const OvenLiveKit = {};
|
|
2
2
|
|
|
3
|
-
const version = '1.
|
|
3
|
+
const version = '1.3.0';
|
|
4
4
|
const logHeader = 'OvenLiveKit.js :';
|
|
5
5
|
const logEventHeader = 'OvenLiveKit.js ====';
|
|
6
6
|
|
|
7
7
|
// private methods
|
|
8
8
|
function sendMessage(webSocket, message) {
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
if (webSocket) {
|
|
11
|
+
webSocket.send(JSON.stringify(message));
|
|
12
|
+
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function generateDomainFromUrl(url) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
let result = '';
|
|
17
|
+
let match;
|
|
18
|
+
if (match = url.match(/^(?:wss?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n\?\=]+)/im)) {
|
|
19
|
+
result = match[1];
|
|
20
|
+
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
return result;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function findIp(string) {
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
let result = '';
|
|
28
|
+
let match;
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
if (match = string.match(new RegExp('\\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b', 'gi'))) {
|
|
31
|
+
result = match[0];
|
|
32
|
+
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
return result;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function getFormatNumber(sdp, format) {
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
const lines = sdp.split('\r\n');
|
|
40
|
+
let formatNumber = -1;
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
lines[i] = lines[i].toLowerCase();
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
46
|
+
if (lines[i].indexOf('a=rtpmap') === 0 && lines[i].indexOf(format.toLowerCase()) > -1) {
|
|
47
|
+
// parsing "a=rtpmap:100 H264/90000" line
|
|
48
|
+
// a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding parameters >]
|
|
49
|
+
formatNumber = lines[i].split(' ')[0].split(':')[1];
|
|
50
|
+
break;
|
|
52
51
|
}
|
|
52
|
+
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
return formatNumber;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function setPreferredVideoFormat(sdp, formatName) {
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
const formatNumber = getFormatNumber(sdp, formatName);
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
if (formatNumber === -1) {
|
|
62
|
+
return sdp;
|
|
63
|
+
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
let newLines = [];
|
|
66
|
+
const lines = sdp.split('\r\n');
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
const line = lines[i];
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// m=<media> <port>/<number of ports> <transport> <fmt list>
|
|
75
|
-
const others = line.split(' ').slice(0, 3);
|
|
76
|
-
const formats = line.split(' ').slice(3);
|
|
77
|
-
formats.sort(function (x, y) { return x == formatNumber ? -1 : y == formatNumber ? 1 : 0; });
|
|
78
|
-
newLines.push(others.concat(formats).join(' '));
|
|
79
|
-
} else {
|
|
80
|
-
newLines.push(line);
|
|
81
|
-
}
|
|
72
|
+
if (line.indexOf('m=video') === 0) {
|
|
82
73
|
|
|
74
|
+
// m=<media> <port>/<number of ports> <transport> <fmt list>
|
|
75
|
+
const others = line.split(' ').slice(0, 3);
|
|
76
|
+
const formats = line.split(' ').slice(3);
|
|
77
|
+
formats.sort(function (x, y) { return x == formatNumber ? -1 : y == formatNumber ? 1 : 0; });
|
|
78
|
+
newLines.push(others.concat(formats).join(' '));
|
|
79
|
+
} else {
|
|
80
|
+
newLines.push(line);
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return newLines.join('\r\n') + '\r\n';
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function removeFormat(sdp, formatNumber) {
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
let newLines = [];
|
|
90
|
+
let lines = sdp.split('\r\n');
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
for (let i = 0; i < lines.length; i++) {
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
if (lines[i].indexOf('m=video') === 0) {
|
|
95
|
+
newLines.push(lines[i].replace(' ' + formatNumber + '', ''));
|
|
96
|
+
} else if (lines[i].indexOf(formatNumber + '') > -1) {
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
98
|
+
} else {
|
|
99
|
+
newLines.push(lines[i]);
|
|
101
100
|
}
|
|
101
|
+
}
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
return newLines.join('\r\n')
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
async function getStreamForDeviceCheck(type) {
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
// High resolution video constraints makes browser to get maximum resolution of video device.
|
|
109
|
+
const constraints = {
|
|
110
|
+
};
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
if (type === 'both') {
|
|
113
|
+
constraints.audio = true;
|
|
114
|
+
constraints.video = true;
|
|
115
|
+
} else if (type === 'audio') {
|
|
116
|
+
constraints.audio = true;
|
|
117
|
+
} else if (type === 'video') {
|
|
118
|
+
constraints.video = true;
|
|
119
|
+
}
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
return await navigator.mediaDevices.getUserMedia(constraints);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
async function getDevices() {
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
return await navigator.mediaDevices.enumerateDevices();
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
function gotDevices(deviceInfos) {
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
let devices = {
|
|
132
|
+
'audioinput': [],
|
|
133
|
+
'audiooutput': [],
|
|
134
|
+
'videoinput': [],
|
|
135
|
+
'other': [],
|
|
136
|
+
};
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
for (let i = 0; i !== deviceInfos.length; ++i) {
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
const deviceInfo = deviceInfos[i];
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
let info = {};
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
info.deviceId = deviceInfo.deviceId;
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
if (deviceInfo.kind === 'audioinput') {
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
info.label = deviceInfo.label || `microphone ${devices.audioinput.length + 1}`;
|
|
149
|
+
devices.audioinput.push(info);
|
|
150
|
+
} else if (deviceInfo.kind === 'audiooutput') {
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
info.label = deviceInfo.label || `speaker ${devices.audiooutput.length + 1}`;
|
|
153
|
+
devices.audiooutput.push(info);
|
|
154
|
+
} else if (deviceInfo.kind === 'videoinput') {
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
info.label = deviceInfo.label || `camera ${devices.videoinput.length + 1}`;
|
|
157
|
+
devices.videoinput.push(info);
|
|
158
|
+
} else {
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
160
|
+
info.label = deviceInfo.label || `other ${devices.other.length + 1}`;
|
|
161
|
+
devices.other.push(info);
|
|
163
162
|
}
|
|
163
|
+
}
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
return devices;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
function initConfig(instance, options) {
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
instance.peerConnection = null;
|
|
173
|
-
instance.connectionConfig = {};
|
|
170
|
+
// webrtc or whip
|
|
171
|
+
instance.streamingMode = null;
|
|
174
172
|
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
instance.inputStream = null;
|
|
174
|
+
instance.webSocket = null;
|
|
175
|
+
instance.peerConnection = null;
|
|
176
|
+
instance.connectionConfig = {};
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
instance.videoElement = null;
|
|
179
|
+
instance.endpointUrl = null;
|
|
180
|
+
instance.resourceUrl = null;
|
|
179
181
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
if (options && options.callbacks) {
|
|
183
|
+
|
|
184
|
+
instance.callbacks = options.callbacks;
|
|
185
|
+
} else {
|
|
186
|
+
instance.callbacks = {};
|
|
187
|
+
}
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
function addMethod(instance) {
|
|
187
191
|
|
|
188
|
-
|
|
192
|
+
function errorHandler(error) {
|
|
189
193
|
|
|
190
|
-
|
|
194
|
+
if (instance.callbacks.error) {
|
|
191
195
|
|
|
192
|
-
|
|
193
|
-
}
|
|
196
|
+
instance.callbacks.error(error);
|
|
194
197
|
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function fetchWithRedirect(url, options) {
|
|
201
|
+
let fetched = await fetch(url, options);
|
|
202
|
+
|
|
203
|
+
while (fetched.redirected) {
|
|
204
|
+
url = fetched.url;
|
|
205
|
+
fetched = await fetch(url, options);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return fetched;
|
|
209
|
+
}
|
|
195
210
|
|
|
196
|
-
|
|
211
|
+
function getUserMedia(constraints) {
|
|
197
212
|
|
|
198
|
-
|
|
213
|
+
if (!constraints) {
|
|
199
214
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
};
|
|
215
|
+
constraints = {
|
|
216
|
+
video: {
|
|
217
|
+
deviceId: undefined
|
|
218
|
+
},
|
|
219
|
+
audio: {
|
|
220
|
+
deviceId: undefined
|
|
208
221
|
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
209
224
|
|
|
210
|
-
|
|
225
|
+
console.info(logHeader, 'Request Stream To Input Devices With Constraints', constraints);
|
|
211
226
|
|
|
212
|
-
|
|
213
|
-
|
|
227
|
+
return navigator.mediaDevices.getUserMedia(constraints)
|
|
228
|
+
.then(function (stream) {
|
|
214
229
|
|
|
215
|
-
|
|
230
|
+
console.info(logHeader, 'Received Media Stream From Input Device', stream);
|
|
216
231
|
|
|
217
|
-
|
|
232
|
+
instance.inputStream = stream;
|
|
218
233
|
|
|
219
|
-
|
|
234
|
+
let elem = instance.videoElement;
|
|
220
235
|
|
|
221
|
-
|
|
222
|
-
|
|
236
|
+
// Attach stream to video element when video element is provided.
|
|
237
|
+
if (elem) {
|
|
223
238
|
|
|
224
|
-
|
|
239
|
+
elem.srcObject = stream;
|
|
225
240
|
|
|
226
|
-
|
|
241
|
+
elem.onloadedmetadata = function (e) {
|
|
227
242
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
243
|
+
elem.play();
|
|
244
|
+
};
|
|
245
|
+
}
|
|
231
246
|
|
|
232
|
-
|
|
247
|
+
return new Promise(function (resolve) {
|
|
233
248
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
249
|
+
resolve(stream);
|
|
250
|
+
});
|
|
251
|
+
})
|
|
252
|
+
.catch(function (error) {
|
|
238
253
|
|
|
239
|
-
|
|
240
|
-
|
|
254
|
+
console.error(logHeader, 'Can\'t Get Media Stream From Input Device', error);
|
|
255
|
+
errorHandler(error);
|
|
241
256
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
257
|
+
return new Promise(function (resolve, reject) {
|
|
258
|
+
reject(error);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function getDisplayMedia(constraints) {
|
|
264
|
+
|
|
265
|
+
if (!constraints) {
|
|
266
|
+
constraints = {};
|
|
246
267
|
}
|
|
247
268
|
|
|
248
|
-
|
|
269
|
+
console.info(logHeader, 'Request Stream To Display With Constraints', constraints);
|
|
270
|
+
|
|
271
|
+
return navigator.mediaDevices.getDisplayMedia(constraints)
|
|
272
|
+
.then(function (stream) {
|
|
273
|
+
|
|
274
|
+
console.info(logHeader, 'Received Media Stream From Display', stream);
|
|
275
|
+
|
|
276
|
+
instance.inputStream = stream;
|
|
277
|
+
|
|
278
|
+
let elem = instance.videoElement;
|
|
279
|
+
|
|
280
|
+
// Attach stream to video element when video element is provided.
|
|
281
|
+
if (elem) {
|
|
282
|
+
|
|
283
|
+
elem.srcObject = stream;
|
|
249
284
|
|
|
250
|
-
|
|
251
|
-
|
|
285
|
+
elem.onloadedmetadata = function (e) {
|
|
286
|
+
|
|
287
|
+
elem.play();
|
|
288
|
+
};
|
|
252
289
|
}
|
|
253
290
|
|
|
254
|
-
|
|
291
|
+
return new Promise(function (resolve) {
|
|
255
292
|
|
|
256
|
-
|
|
257
|
-
|
|
293
|
+
resolve(stream);
|
|
294
|
+
});
|
|
295
|
+
})
|
|
296
|
+
.catch(function (error) {
|
|
258
297
|
|
|
259
|
-
|
|
298
|
+
console.error(logHeader, 'Can\'t Get Media Stream From Display', error);
|
|
299
|
+
errorHandler(error);
|
|
260
300
|
|
|
261
|
-
|
|
301
|
+
return new Promise(function (resolve, reject) {
|
|
302
|
+
reject(error);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
}
|
|
262
306
|
|
|
263
|
-
|
|
307
|
+
function setMediaStream(stream) {
|
|
308
|
+
// Check if a valid stream is provided
|
|
309
|
+
if (!stream || !(stream instanceof MediaStream)) {
|
|
264
310
|
|
|
265
|
-
|
|
266
|
-
|
|
311
|
+
const error = new Error("Invalid MediaStream provided");
|
|
312
|
+
console.error(logHeader, 'Invalid MediaStream', error);
|
|
313
|
+
errorHandler(error);
|
|
267
314
|
|
|
268
|
-
|
|
315
|
+
return new Promise(function (resolve, reject) {
|
|
316
|
+
reject(error);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
269
319
|
|
|
270
|
-
|
|
320
|
+
console.info(logHeader, 'Received Media Stream', stream);
|
|
271
321
|
|
|
272
|
-
|
|
273
|
-
};
|
|
274
|
-
}
|
|
322
|
+
instance.inputStream = stream;
|
|
275
323
|
|
|
276
|
-
|
|
324
|
+
let elem = instance.videoElement;
|
|
277
325
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
.catch(function (error) {
|
|
326
|
+
// Attach stream to video element when video element is provided.
|
|
327
|
+
if (elem) {
|
|
328
|
+
elem.srcObject = stream;
|
|
282
329
|
|
|
283
|
-
|
|
284
|
-
|
|
330
|
+
elem.onloadedmetadata = function (e) {
|
|
331
|
+
elem.play();
|
|
332
|
+
};
|
|
333
|
+
}
|
|
285
334
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
335
|
+
return new Promise(function (resolve) {
|
|
336
|
+
resolve(stream);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// From https://webrtchacks.com/limit-webrtc-bandwidth-sdp/
|
|
341
|
+
function setBitrateLimit(sdp, media, bitrate) {
|
|
342
|
+
|
|
343
|
+
let lines = sdp.split('\r\n');
|
|
344
|
+
let line = -1;
|
|
345
|
+
|
|
346
|
+
for (let i = 0; i < lines.length; i++) {
|
|
347
|
+
if (lines[i].indexOf('m=' + media) === 0) {
|
|
348
|
+
line = i;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (line === -1) {
|
|
353
|
+
// Could not find the m line for media
|
|
354
|
+
return sdp;
|
|
290
355
|
}
|
|
291
356
|
|
|
292
|
-
//
|
|
293
|
-
|
|
357
|
+
// Pass the m line
|
|
358
|
+
line++;
|
|
294
359
|
|
|
295
|
-
|
|
296
|
-
|
|
360
|
+
// Skip i and c lines
|
|
361
|
+
while (lines[line].indexOf('i=') === 0 || lines[line].indexOf('c=') === 0) {
|
|
297
362
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
line = i;
|
|
301
|
-
break;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
if (line === -1) {
|
|
305
|
-
// Could not find the m line for media
|
|
306
|
-
return sdp;
|
|
307
|
-
}
|
|
363
|
+
line++;
|
|
364
|
+
}
|
|
308
365
|
|
|
309
|
-
|
|
310
|
-
|
|
366
|
+
// If we're on a b line, replace it
|
|
367
|
+
if (lines[line].indexOf('b') === 0) {
|
|
311
368
|
|
|
312
|
-
|
|
313
|
-
while (lines[line].indexOf('i=') === 0 || lines[line].indexOf('c=') === 0) {
|
|
369
|
+
lines[line] = 'b=AS:' + bitrate;
|
|
314
370
|
|
|
315
|
-
|
|
316
|
-
|
|
371
|
+
return lines.join('\r\n');
|
|
372
|
+
}
|
|
317
373
|
|
|
318
|
-
|
|
319
|
-
|
|
374
|
+
// Add a new b line
|
|
375
|
+
let newLines = lines.slice(0, line)
|
|
320
376
|
|
|
321
|
-
|
|
377
|
+
newLines.push('b=AS:' + bitrate)
|
|
378
|
+
newLines = newLines.concat(lines.slice(line, lines.length))
|
|
322
379
|
|
|
323
|
-
|
|
324
|
-
|
|
380
|
+
return newLines.join('\r\n')
|
|
381
|
+
}
|
|
325
382
|
|
|
326
|
-
|
|
327
|
-
let newLines = lines.slice(0, line)
|
|
383
|
+
function initWebSocket(endpointUrl) {
|
|
328
384
|
|
|
329
|
-
|
|
330
|
-
|
|
385
|
+
if (!endpointUrl) {
|
|
386
|
+
errorHandler('endpointUrl is required');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let webSocket = null;
|
|
391
|
+
|
|
392
|
+
try {
|
|
331
393
|
|
|
332
|
-
|
|
394
|
+
webSocket = new WebSocket(endpointUrl);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
|
|
397
|
+
errorHandler(error);
|
|
333
398
|
}
|
|
334
399
|
|
|
335
|
-
function initWebSocket(connectionUrl) {
|
|
336
400
|
|
|
337
|
-
|
|
338
|
-
errorHandler('connectionUrl is required');
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
401
|
+
instance.webSocket = webSocket;
|
|
341
402
|
|
|
342
|
-
|
|
403
|
+
webSocket.onopen = function () {
|
|
343
404
|
|
|
344
|
-
|
|
405
|
+
// Request offer at the first time.
|
|
406
|
+
sendMessage(webSocket, {
|
|
407
|
+
command: 'request_offer'
|
|
408
|
+
});
|
|
409
|
+
};
|
|
345
410
|
|
|
346
|
-
|
|
411
|
+
webSocket.onmessage = function (e) {
|
|
347
412
|
|
|
348
|
-
|
|
349
|
-
} catch (error) {
|
|
413
|
+
let message = JSON.parse(e.data);
|
|
350
414
|
|
|
351
|
-
|
|
415
|
+
if (message.error) {
|
|
416
|
+
console.error('webSocket.onmessage', message.error);
|
|
417
|
+
errorHandler(message.error);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (message.command === 'offer') {
|
|
421
|
+
|
|
422
|
+
// OME returns offer. Start create peer connection.
|
|
423
|
+
createPeerConnection(
|
|
424
|
+
message.id,
|
|
425
|
+
message.peer_id,
|
|
426
|
+
message.sdp,
|
|
427
|
+
message.candidates,
|
|
428
|
+
message.ice_servers
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
webSocket.onerror = function (error) {
|
|
434
|
+
|
|
435
|
+
console.error('webSocket.onerror', error);
|
|
436
|
+
errorHandler(error);
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
webSocket.onclose = function (e) {
|
|
440
|
+
|
|
441
|
+
if (!instance.webSocketClosedByUser) {
|
|
442
|
+
|
|
443
|
+
if (instance.callbacks.connectionClosed) {
|
|
444
|
+
instance.callbacks.connectionClosed('websocket', e);
|
|
352
445
|
}
|
|
446
|
+
}
|
|
447
|
+
};
|
|
353
448
|
|
|
449
|
+
}
|
|
354
450
|
|
|
355
|
-
|
|
451
|
+
async function startWhip(endpointUrl) {
|
|
356
452
|
|
|
357
|
-
|
|
453
|
+
if (instance.peerConnection) {
|
|
454
|
+
console.error('Connection already established');
|
|
455
|
+
errorHandler('Connection already established');
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
358
458
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
});
|
|
363
|
-
};
|
|
459
|
+
const peerConnectionConfig = {
|
|
460
|
+
bundlePolicy: "max-bundle"
|
|
461
|
+
};
|
|
364
462
|
|
|
365
|
-
|
|
463
|
+
if (instance.connectionConfig.iceServers) {
|
|
366
464
|
|
|
367
|
-
|
|
465
|
+
// first priority using ice servers from local config.
|
|
466
|
+
peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;
|
|
368
467
|
|
|
369
|
-
|
|
370
|
-
console.error('webSocket.onmessage', message.error);
|
|
371
|
-
errorHandler(message.error);
|
|
372
|
-
}
|
|
468
|
+
if (instance.connectionConfig.iceTransportPolicy) {
|
|
373
469
|
|
|
374
|
-
|
|
470
|
+
peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;
|
|
471
|
+
}
|
|
472
|
+
} else {
|
|
473
|
+
// last priority using default ice servers.
|
|
375
474
|
|
|
376
|
-
|
|
377
|
-
createPeerConnection(
|
|
378
|
-
message.id,
|
|
379
|
-
message.peer_id,
|
|
380
|
-
message.sdp,
|
|
381
|
-
message.candidates,
|
|
382
|
-
message.ice_servers
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
};
|
|
475
|
+
if (instance.connectionConfig.iceTransportPolicy) {
|
|
386
476
|
|
|
387
|
-
|
|
477
|
+
peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
388
480
|
|
|
389
|
-
|
|
390
|
-
errorHandler(error);
|
|
391
|
-
};
|
|
481
|
+
console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);
|
|
392
482
|
|
|
393
|
-
|
|
483
|
+
const peerConnection = new RTCPeerConnection(peerConnectionConfig);
|
|
394
484
|
|
|
395
|
-
|
|
485
|
+
instance.peerConnection = peerConnection;
|
|
396
486
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
487
|
+
if (!instance.inputStream) {
|
|
488
|
+
console.error('No input stream in OvenLiveKit');
|
|
489
|
+
errorHandler('No input stream in OvenLiveKit');
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
402
492
|
|
|
493
|
+
for (const track of instance.inputStream.getTracks()) {
|
|
494
|
+
//You could add simulcast too here
|
|
495
|
+
console.log(logHeader, 'Adding track: ', track);
|
|
496
|
+
peerConnection.addTransceiver(track, { 'direction': 'sendonly' });
|
|
403
497
|
}
|
|
404
498
|
|
|
405
|
-
function
|
|
499
|
+
peerConnection.oniceconnectionstatechange = function (e) {
|
|
406
500
|
|
|
407
|
-
|
|
501
|
+
let state = peerConnection.iceConnectionState;
|
|
408
502
|
|
|
409
|
-
|
|
410
|
-
const payloads = [];
|
|
503
|
+
if (instance.callbacks.iceStateChange) {
|
|
411
504
|
|
|
412
|
-
|
|
505
|
+
console.info(logHeader, 'ICE State', '[' + state + ']');
|
|
506
|
+
instance.callbacks.iceStateChange(state);
|
|
507
|
+
}
|
|
413
508
|
|
|
414
|
-
|
|
509
|
+
if (state === 'connected') {
|
|
415
510
|
|
|
416
|
-
|
|
511
|
+
if (instance.callbacks.connected) {
|
|
512
|
+
instance.callbacks.connected(e);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
417
515
|
|
|
418
|
-
|
|
516
|
+
if (state === 'failed') {
|
|
419
517
|
|
|
420
|
-
|
|
421
|
-
|
|
518
|
+
if (instance.callbacks.connectionClosed) {
|
|
519
|
+
console.error(logHeader, 'Ice connection failed', e);
|
|
520
|
+
instance.callbacks.errorHandler(e);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
422
523
|
|
|
423
|
-
|
|
424
|
-
|
|
524
|
+
if (state === 'disconnected' || state === 'closed') {
|
|
525
|
+
|
|
526
|
+
if (instance.callbacks.connectionClosed) {
|
|
527
|
+
console.error(logHeader, 'Ice connection disconnected or closed', e);
|
|
528
|
+
instance.callbacks.connectionClosed('ice', e);
|
|
425
529
|
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
426
532
|
|
|
427
|
-
|
|
533
|
+
const offer = await peerConnection.createOffer();
|
|
534
|
+
console.log(logHeader, 'Offer SDP: ', offer.sdp);
|
|
428
535
|
|
|
429
|
-
|
|
536
|
+
if (instance.connectionConfig.maxVideoBitrate) {
|
|
430
537
|
|
|
431
|
-
|
|
538
|
+
// if bandwith limit is set. modify sdp from ome to limit acceptable bandwidth of ome
|
|
539
|
+
offer.sdp = setBitrateLimit(offer.sdp, 'video', instance.connectionConfig.maxVideoBitrate);
|
|
540
|
+
}
|
|
432
541
|
|
|
433
|
-
|
|
434
|
-
fmtpLineFound = true;
|
|
435
|
-
lines[j] += ';' + fmtpStr;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
542
|
+
if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {
|
|
438
543
|
|
|
439
|
-
|
|
544
|
+
offer.sdp = appendFmtp(offer.sdp);
|
|
545
|
+
}
|
|
440
546
|
|
|
441
|
-
|
|
547
|
+
if (instance.connectionConfig.preferredVideoFormat) {
|
|
548
|
+
offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat)
|
|
549
|
+
}
|
|
442
550
|
|
|
443
|
-
|
|
551
|
+
const headers = {
|
|
552
|
+
"Content-Type": "application/sdp"
|
|
553
|
+
};
|
|
444
554
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
555
|
+
if (instance.connectionConfig.httpHeaders) {
|
|
556
|
+
Object.assign(headers, instance.connectionConfig.httpHeaders);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const fetched = await fetchWithRedirect(endpointUrl, {
|
|
560
|
+
method: "POST",
|
|
561
|
+
body: offer.sdp,
|
|
562
|
+
headers
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
if (!fetched.ok) {
|
|
566
|
+
console.error('Failed to fetch', fetched.status);
|
|
567
|
+
errorHandler(`Failed to fetch ${fetched.status}`);
|
|
568
|
+
closePeerConnection();
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (!fetched.headers.get("location")) {
|
|
573
|
+
console.error('No location header on answer response');
|
|
574
|
+
errorHandler('No location header on answer response');
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// update endpointUrl
|
|
579
|
+
instance.endpointUrl = fetched.url;
|
|
580
|
+
console.log(logHeader, 'Updated endpointUrl: ', instance.endpointUrl);
|
|
581
|
+
|
|
582
|
+
const baseUrl = new URL(endpointUrl).origin;
|
|
583
|
+
instance.resourceUrl = baseUrl + fetched.headers.get("location");
|
|
584
|
+
|
|
585
|
+
const answer = await fetched.text();
|
|
586
|
+
console.log(logHeader, 'Answer SDP: ', answer);
|
|
587
|
+
|
|
588
|
+
try {
|
|
589
|
+
await peerConnection.setLocalDescription(offer);
|
|
590
|
+
} catch (error) {
|
|
591
|
+
console.error('peerConnection.setLocalDescription', error);
|
|
592
|
+
errorHandler(error);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
try {
|
|
596
|
+
await peerConnection.setRemoteDescription({
|
|
597
|
+
type: "answer",
|
|
598
|
+
sdp: answer
|
|
599
|
+
});
|
|
600
|
+
} catch (error) {
|
|
601
|
+
console.error('peerConnection.setRemoteDescription', error);
|
|
602
|
+
errorHandler(error);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async function stopWhip() {
|
|
607
|
+
|
|
608
|
+
if (!instance.peerConnection) {
|
|
609
|
+
console.error('No connection to close');
|
|
610
|
+
errorHandler('No connection to close');
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
closePeerConnection();
|
|
615
|
+
|
|
616
|
+
if (instance.resourceUrl) {
|
|
617
|
+
|
|
618
|
+
const headers = {
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
if (instance.connectionConfig.httpHeaders) {
|
|
622
|
+
Object.assign(headers, instance.connectionConfig.httpHeaders);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
await fetchWithRedirect(instance.resourceUrl, {
|
|
626
|
+
method: "DELETE",
|
|
627
|
+
headers
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function appendFmtp(sdp) {
|
|
633
|
+
|
|
634
|
+
const fmtpStr = instance.connectionConfig.sdp.appendFmtp;
|
|
635
|
+
|
|
636
|
+
const lines = sdp.split('\r\n');
|
|
637
|
+
const payloads = [];
|
|
638
|
+
|
|
639
|
+
for (let i = 0; i < lines.length; i++) {
|
|
640
|
+
|
|
641
|
+
if (lines[i].indexOf('m=video') === 0) {
|
|
642
|
+
|
|
643
|
+
let tokens = lines[i].split(' ')
|
|
644
|
+
|
|
645
|
+
for (let j = 3; j < tokens.length; j++) {
|
|
646
|
+
|
|
647
|
+
payloads.push(tokens[j]);
|
|
449
648
|
}
|
|
450
649
|
|
|
451
|
-
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
452
652
|
}
|
|
453
653
|
|
|
454
|
-
|
|
654
|
+
for (let i = 0; i < payloads.length; i++) {
|
|
455
655
|
|
|
456
|
-
|
|
457
|
-
const payloads = [];
|
|
656
|
+
let fmtpLineFound = false;
|
|
458
657
|
|
|
459
|
-
|
|
658
|
+
for (let j = 0; j < lines.length; j++) {
|
|
460
659
|
|
|
461
|
-
|
|
660
|
+
if (lines[j].indexOf('a=fmtp:' + payloads[i]) === 0) {
|
|
661
|
+
fmtpLineFound = true;
|
|
662
|
+
lines[j] += ';' + fmtpStr;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
462
665
|
|
|
463
|
-
|
|
666
|
+
if (!fmtpLineFound) {
|
|
464
667
|
|
|
465
|
-
|
|
668
|
+
for (let j = 0; j < lines.length; j++) {
|
|
466
669
|
|
|
467
|
-
|
|
468
|
-
}
|
|
670
|
+
if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {
|
|
469
671
|
|
|
470
|
-
|
|
471
|
-
|
|
672
|
+
lines[j] += '\r\na=fmtp:' + payloads[i] + ' ' + fmtpStr;
|
|
673
|
+
}
|
|
472
674
|
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
473
677
|
|
|
474
|
-
|
|
678
|
+
return lines.join('\r\n')
|
|
679
|
+
}
|
|
475
680
|
|
|
476
|
-
|
|
681
|
+
function appendOrientation(sdp) {
|
|
477
682
|
|
|
478
|
-
|
|
683
|
+
const lines = sdp.split('\r\n');
|
|
684
|
+
const payloads = [];
|
|
479
685
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
686
|
+
for (let i = 0; i < lines.length; i++) {
|
|
687
|
+
|
|
688
|
+
if (lines[i].indexOf('m=video') === 0) {
|
|
689
|
+
|
|
690
|
+
let tokens = lines[i].split(' ')
|
|
691
|
+
|
|
692
|
+
for (let j = 3; j < tokens.length; j++) {
|
|
693
|
+
|
|
694
|
+
payloads.push(tokens[j]);
|
|
483
695
|
}
|
|
484
696
|
|
|
485
|
-
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
486
699
|
}
|
|
487
700
|
|
|
488
|
-
|
|
701
|
+
for (let i = 0; i < payloads.length; i++) {
|
|
489
702
|
|
|
490
|
-
|
|
703
|
+
for (let j = 0; j < lines.length; j++) {
|
|
491
704
|
|
|
492
|
-
if (
|
|
705
|
+
if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {
|
|
493
706
|
|
|
494
|
-
|
|
495
|
-
|
|
707
|
+
lines[j] += '\r\na=extmap:' + payloads[i] + ' urn:3gpp:video-orientation';
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
496
711
|
|
|
497
|
-
|
|
712
|
+
return lines.join('\r\n')
|
|
713
|
+
}
|
|
498
714
|
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
} else if (iceServers) {
|
|
715
|
+
function createPeerConnection(id, peerId, offer, candidates, iceServers) {
|
|
502
716
|
|
|
503
|
-
|
|
504
|
-
peerConnectionConfig.iceServers = [];
|
|
717
|
+
let peerConnectionConfig = {};
|
|
505
718
|
|
|
506
|
-
|
|
719
|
+
if (instance.connectionConfig.iceServers) {
|
|
507
720
|
|
|
508
|
-
|
|
721
|
+
// first priority using ice servers from local config.
|
|
722
|
+
peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;
|
|
509
723
|
|
|
510
|
-
|
|
724
|
+
if (instance.connectionConfig.iceTransportPolicy) {
|
|
511
725
|
|
|
512
|
-
|
|
726
|
+
peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;
|
|
727
|
+
}
|
|
728
|
+
} else if (iceServers) {
|
|
513
729
|
|
|
514
|
-
|
|
515
|
-
|
|
730
|
+
// second priority using ice servers from ome and force using TCP
|
|
731
|
+
peerConnectionConfig.iceServers = [];
|
|
516
732
|
|
|
517
|
-
|
|
733
|
+
for (let i = 0; i < iceServers.length; i++) {
|
|
518
734
|
|
|
519
|
-
|
|
735
|
+
let iceServer = iceServers[i];
|
|
520
736
|
|
|
521
|
-
|
|
522
|
-
hasWebSocketUrl = true;
|
|
523
|
-
break;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
737
|
+
let regIceServer = {};
|
|
526
738
|
|
|
527
|
-
|
|
739
|
+
regIceServer.urls = iceServer.urls;
|
|
528
740
|
|
|
529
|
-
|
|
741
|
+
let hasWebSocketUrl = false;
|
|
742
|
+
let webSocketUrl = generateDomainFromUrl(instance.endpointUrl);
|
|
530
743
|
|
|
531
|
-
|
|
532
|
-
let ip = findIp(cloneIceServer);
|
|
744
|
+
for (let j = 0; j < regIceServer.urls.length; j++) {
|
|
533
745
|
|
|
534
|
-
|
|
535
|
-
regIceServer.urls.push(cloneIceServer.replace(ip, webSocketUrl));
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
746
|
+
let serverUrl = regIceServer.urls[j];
|
|
539
747
|
|
|
540
|
-
|
|
541
|
-
|
|
748
|
+
if (serverUrl.indexOf(webSocketUrl) > -1) {
|
|
749
|
+
hasWebSocketUrl = true;
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
542
753
|
|
|
543
|
-
|
|
544
|
-
}
|
|
754
|
+
if (!hasWebSocketUrl) {
|
|
545
755
|
|
|
546
|
-
|
|
547
|
-
} else {
|
|
548
|
-
// last priority using default ice servers.
|
|
756
|
+
if (regIceServer.urls.length > 0) {
|
|
549
757
|
|
|
550
|
-
|
|
758
|
+
let cloneIceServer = regIceServer.urls[0];
|
|
759
|
+
let ip = findIp(cloneIceServer);
|
|
551
760
|
|
|
552
|
-
|
|
761
|
+
if (webSocketUrl && ip) {
|
|
762
|
+
regIceServer.urls.push(cloneIceServer.replace(ip, webSocketUrl));
|
|
553
763
|
}
|
|
764
|
+
}
|
|
554
765
|
}
|
|
555
766
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
{
|
|
559
|
-
googHighStartBitrate: {
|
|
560
|
-
exact: !0
|
|
561
|
-
}
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
googPayloadPadding: {
|
|
565
|
-
exact: !0
|
|
566
|
-
}
|
|
567
|
-
},
|
|
568
|
-
{
|
|
569
|
-
googScreencastMinBitrate: {
|
|
570
|
-
exact: 500
|
|
571
|
-
}
|
|
572
|
-
},
|
|
573
|
-
{
|
|
574
|
-
enableDscp: {
|
|
575
|
-
exact: true
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
]
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);
|
|
582
|
-
|
|
583
|
-
let peerConnection = new RTCPeerConnection(peerConnectionConfig);
|
|
584
|
-
|
|
585
|
-
instance.peerConnection = peerConnection;
|
|
586
|
-
|
|
587
|
-
// set local stream
|
|
588
|
-
instance.inputStream.getTracks().forEach(function (track) {
|
|
589
|
-
|
|
590
|
-
console.info(logHeader, 'Add Track To Peer Connection', track);
|
|
591
|
-
peerConnection.addTrack(track, instance.inputStream);
|
|
592
|
-
});
|
|
767
|
+
regIceServer.username = iceServer.user_name;
|
|
768
|
+
regIceServer.credential = iceServer.credential;
|
|
593
769
|
|
|
594
|
-
|
|
770
|
+
peerConnectionConfig.iceServers.push(regIceServer);
|
|
771
|
+
}
|
|
595
772
|
|
|
596
|
-
|
|
597
|
-
offer.sdp = setBitrateLimit(offer.sdp, 'video', instance.connectionConfig.maxVideoBitrate);
|
|
598
|
-
}
|
|
773
|
+
if (instance.connectionConfig.iceTransportPolicy) {
|
|
599
774
|
|
|
600
|
-
|
|
775
|
+
peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;
|
|
776
|
+
} else {
|
|
777
|
+
peerConnectionConfig.iceTransportPolicy = 'relay';
|
|
778
|
+
}
|
|
779
|
+
} else {
|
|
780
|
+
// last priority using default ice servers.
|
|
601
781
|
|
|
602
|
-
|
|
603
|
-
}
|
|
782
|
+
if (instance.connectionConfig.iceTransportPolicy) {
|
|
604
783
|
|
|
605
|
-
|
|
606
|
-
|
|
784
|
+
peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
let advancedSetting = {
|
|
789
|
+
optional: [
|
|
790
|
+
{
|
|
791
|
+
googHighStartBitrate: {
|
|
792
|
+
exact: !0
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
googPayloadPadding: {
|
|
797
|
+
exact: !0
|
|
798
|
+
}
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
googScreencastMinBitrate: {
|
|
802
|
+
exact: 500
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
enableDscp: {
|
|
807
|
+
exact: true
|
|
808
|
+
}
|
|
607
809
|
}
|
|
810
|
+
]
|
|
811
|
+
};
|
|
608
812
|
|
|
813
|
+
console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);
|
|
609
814
|
|
|
610
|
-
|
|
611
|
-
console.info(logHeader, 'Modified offer sdp\n\n' + offer.sdp);
|
|
815
|
+
let peerConnection = new RTCPeerConnection(peerConnectionConfig);
|
|
612
816
|
|
|
613
|
-
|
|
614
|
-
.then(function () {
|
|
817
|
+
instance.peerConnection = peerConnection;
|
|
615
818
|
|
|
616
|
-
|
|
617
|
-
|
|
819
|
+
// set local stream
|
|
820
|
+
instance.inputStream.getTracks().forEach(function (track) {
|
|
618
821
|
|
|
619
|
-
|
|
822
|
+
console.info(logHeader, 'Add Track To Peer Connection', track);
|
|
823
|
+
peerConnection.addTrack(track, instance.inputStream);
|
|
824
|
+
});
|
|
620
825
|
|
|
621
|
-
|
|
622
|
-
}
|
|
826
|
+
if (instance.connectionConfig.maxVideoBitrate) {
|
|
623
827
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
828
|
+
// if bandwith limit is set. modify sdp from ome to limit acceptable bandwidth of ome
|
|
829
|
+
offer.sdp = setBitrateLimit(offer.sdp, 'video', instance.connectionConfig.maxVideoBitrate);
|
|
830
|
+
}
|
|
627
831
|
|
|
628
|
-
|
|
832
|
+
if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {
|
|
629
833
|
|
|
630
|
-
|
|
631
|
-
|
|
834
|
+
offer.sdp = appendFmtp(offer.sdp);
|
|
835
|
+
}
|
|
632
836
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
command: 'answer',
|
|
637
|
-
sdp: answer
|
|
638
|
-
});
|
|
639
|
-
})
|
|
640
|
-
.catch(function (error) {
|
|
837
|
+
if (instance.connectionConfig.preferredVideoFormat) {
|
|
838
|
+
offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat)
|
|
839
|
+
}
|
|
641
840
|
|
|
642
|
-
console.error('peerConnection.setLocalDescription', error);
|
|
643
|
-
errorHandler(error);
|
|
644
|
-
});
|
|
645
|
-
})
|
|
646
|
-
.catch(function (error) {
|
|
647
841
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
});
|
|
651
|
-
})
|
|
652
|
-
.catch(function (error) {
|
|
842
|
+
// offer.sdp = appendOrientation(offer.sdp);
|
|
843
|
+
console.info(logHeader, 'Modified offer sdp\n\n' + offer.sdp);
|
|
653
844
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
});
|
|
845
|
+
peerConnection.setRemoteDescription(new RTCSessionDescription(offer))
|
|
846
|
+
.then(function () {
|
|
657
847
|
|
|
658
|
-
|
|
848
|
+
peerConnection.createAnswer()
|
|
849
|
+
.then(function (answer) {
|
|
659
850
|
|
|
660
|
-
|
|
661
|
-
|
|
851
|
+
if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {
|
|
852
|
+
|
|
853
|
+
answer.sdp = appendFmtp(answer.sdp);
|
|
854
|
+
}
|
|
662
855
|
|
|
663
|
-
|
|
856
|
+
if (instance.connectionConfig.preferredVideoFormat) {
|
|
857
|
+
answer.sdp = setPreferredVideoFormat(answer.sdp, instance.connectionConfig.preferredVideoFormat)
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
console.info(logHeader, 'Modified answer sdp\n\n' + answer.sdp);
|
|
664
861
|
|
|
665
|
-
|
|
862
|
+
peerConnection.setLocalDescription(answer)
|
|
863
|
+
.then(function () {
|
|
666
864
|
|
|
667
865
|
sendMessage(instance.webSocket, {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
866
|
+
id: id,
|
|
867
|
+
peer_id: peerId,
|
|
868
|
+
command: 'answer',
|
|
869
|
+
sdp: answer
|
|
672
870
|
});
|
|
673
|
-
|
|
674
|
-
|
|
871
|
+
})
|
|
872
|
+
.catch(function (error) {
|
|
675
873
|
|
|
676
|
-
|
|
874
|
+
console.error('peerConnection.setLocalDescription', error);
|
|
875
|
+
errorHandler(error);
|
|
876
|
+
});
|
|
877
|
+
})
|
|
878
|
+
.catch(function (error) {
|
|
677
879
|
|
|
678
|
-
|
|
880
|
+
console.error('peerConnection.createAnswer', error);
|
|
881
|
+
errorHandler(error);
|
|
882
|
+
});
|
|
883
|
+
})
|
|
884
|
+
.catch(function (error) {
|
|
679
885
|
|
|
680
|
-
|
|
886
|
+
console.error('peerConnection.setRemoteDescription', error);
|
|
887
|
+
errorHandler(error);
|
|
888
|
+
});
|
|
681
889
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
890
|
+
if (candidates) {
|
|
891
|
+
|
|
892
|
+
addIceCandidate(peerConnection, candidates);
|
|
893
|
+
}
|
|
685
894
|
|
|
686
|
-
|
|
895
|
+
peerConnection.onicecandidate = function (e) {
|
|
687
896
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
897
|
+
if (e.candidate && e.candidate.candidate) {
|
|
898
|
+
|
|
899
|
+
sendMessage(instance.webSocket, {
|
|
900
|
+
id: id,
|
|
901
|
+
peer_id: peerId,
|
|
902
|
+
command: 'candidate',
|
|
903
|
+
candidates: [e.candidate]
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
};
|
|
692
907
|
|
|
693
|
-
|
|
908
|
+
peerConnection.oniceconnectionstatechange = function (e) {
|
|
694
909
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
910
|
+
let state = peerConnection.iceConnectionState;
|
|
911
|
+
|
|
912
|
+
if (instance.callbacks.iceStateChange) {
|
|
913
|
+
|
|
914
|
+
console.info(logHeader, 'ICE State', '[' + state + ']');
|
|
915
|
+
instance.callbacks.iceStateChange(state);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (state === 'connected') {
|
|
919
|
+
|
|
920
|
+
if (instance.callbacks.connected) {
|
|
921
|
+
instance.callbacks.connected(e);
|
|
700
922
|
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (state === 'failed' || state === 'disconnected' || state === 'closed') {
|
|
926
|
+
|
|
927
|
+
if (instance.callbacks.connectionClosed) {
|
|
928
|
+
console.error(logHeader, 'Ice connection closed', e);
|
|
929
|
+
instance.callbacks.connectionClosed('ice', e);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
701
932
|
}
|
|
933
|
+
}
|
|
702
934
|
|
|
703
|
-
|
|
935
|
+
function addIceCandidate(peerConnection, candidates) {
|
|
704
936
|
|
|
705
|
-
|
|
937
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
706
938
|
|
|
707
|
-
|
|
939
|
+
if (candidates[i] && candidates[i].candidate) {
|
|
708
940
|
|
|
709
|
-
|
|
941
|
+
let basicCandidate = candidates[i];
|
|
710
942
|
|
|
711
|
-
|
|
712
|
-
|
|
943
|
+
peerConnection.addIceCandidate(new RTCIceCandidate(basicCandidate))
|
|
944
|
+
.then(function () {
|
|
713
945
|
|
|
714
|
-
|
|
715
|
-
|
|
946
|
+
})
|
|
947
|
+
.catch(function (error) {
|
|
716
948
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
}
|
|
949
|
+
console.error('peerConnection.addIceCandidate', error);
|
|
950
|
+
errorHandler(error);
|
|
951
|
+
});
|
|
952
|
+
}
|
|
722
953
|
}
|
|
954
|
+
}
|
|
723
955
|
|
|
724
|
-
|
|
725
|
-
|
|
956
|
+
function closePeerConnection() {
|
|
957
|
+
if (instance.peerConnection) {
|
|
726
958
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
959
|
+
// remove tracks from peer connection
|
|
960
|
+
instance.peerConnection.getSenders().forEach(function (sender) {
|
|
961
|
+
instance.peerConnection.removeTrack(sender);
|
|
962
|
+
});
|
|
731
963
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
}
|
|
964
|
+
instance.peerConnection.close();
|
|
965
|
+
instance.peerConnection = null;
|
|
966
|
+
delete instance.peerConnection;
|
|
736
967
|
}
|
|
968
|
+
}
|
|
737
969
|
|
|
738
|
-
|
|
970
|
+
function closeWebSocket() {
|
|
739
971
|
|
|
740
|
-
|
|
972
|
+
if (instance.webSocket) {
|
|
741
973
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
974
|
+
instance.webSocket.close();
|
|
975
|
+
instance.webSocket = null;
|
|
976
|
+
delete instance.webSocket;
|
|
746
977
|
}
|
|
978
|
+
}
|
|
747
979
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
980
|
+
function closeInputStream() {
|
|
981
|
+
// release video, audio stream
|
|
982
|
+
if (instance.inputStream) {
|
|
751
983
|
|
|
752
|
-
|
|
984
|
+
instance.inputStream.getTracks().forEach(track => {
|
|
753
985
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
986
|
+
track.stop();
|
|
987
|
+
instance.inputStream.removeTrack(track);
|
|
988
|
+
});
|
|
757
989
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
990
|
+
if (instance.videoElement) {
|
|
991
|
+
instance.videoElement.srcObject = null;
|
|
992
|
+
}
|
|
761
993
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
}
|
|
994
|
+
instance.inputStream = null;
|
|
995
|
+
delete instance.inputStream;
|
|
765
996
|
}
|
|
997
|
+
}
|
|
766
998
|
|
|
767
|
-
|
|
768
|
-
|
|
999
|
+
// instance methods
|
|
1000
|
+
instance.attachMedia = function (videoElement) {
|
|
769
1001
|
|
|
770
|
-
|
|
771
|
-
|
|
1002
|
+
instance.videoElement = videoElement;
|
|
1003
|
+
};
|
|
772
1004
|
|
|
773
|
-
|
|
1005
|
+
instance.getUserMedia = function (constraints) {
|
|
774
1006
|
|
|
775
|
-
|
|
776
|
-
|
|
1007
|
+
return getUserMedia(constraints);
|
|
1008
|
+
};
|
|
777
1009
|
|
|
778
|
-
|
|
1010
|
+
instance.getDisplayMedia = function (constraints) {
|
|
779
1011
|
|
|
780
|
-
|
|
781
|
-
|
|
1012
|
+
return getDisplayMedia(constraints);
|
|
1013
|
+
};
|
|
782
1014
|
|
|
783
|
-
|
|
1015
|
+
instance.setMediaStream = function (stream) {
|
|
784
1016
|
|
|
785
|
-
|
|
1017
|
+
return setMediaStream(stream);
|
|
1018
|
+
};
|
|
786
1019
|
|
|
787
|
-
|
|
1020
|
+
instance.startStreaming = function (endpointUrl, connectionConfig) {
|
|
788
1021
|
|
|
789
|
-
|
|
790
|
-
}
|
|
1022
|
+
console.info(logEventHeader, `Start Streaming to ${endpointUrl} with connectionConfig`, connectionConfig);
|
|
791
1023
|
|
|
792
|
-
|
|
793
|
-
|
|
1024
|
+
if (!endpointUrl) {
|
|
1025
|
+
console.error('endpointUrl is required');
|
|
1026
|
+
errorHandler('endpointUrl is required');
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
794
1029
|
|
|
795
|
-
instance.
|
|
1030
|
+
instance.endpointUrl = endpointUrl;
|
|
796
1031
|
|
|
797
|
-
|
|
1032
|
+
if (connectionConfig) {
|
|
1033
|
+
instance.connectionConfig = connectionConfig;
|
|
1034
|
+
}
|
|
798
1035
|
|
|
799
|
-
|
|
800
|
-
closePeerConnection();
|
|
801
|
-
};
|
|
1036
|
+
try {
|
|
802
1037
|
|
|
803
|
-
|
|
1038
|
+
const protocol = new URL(endpointUrl).protocol;
|
|
804
1039
|
|
|
805
|
-
|
|
1040
|
+
if (protocol === 'wss:' || protocol === 'ws:') {
|
|
806
1041
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1042
|
+
instance.streamingMode = 'webrtc';
|
|
1043
|
+
initWebSocket(endpointUrl);
|
|
1044
|
+
} else if (protocol === 'https:' || protocol === 'http:') {
|
|
810
1045
|
|
|
811
|
-
|
|
1046
|
+
instance.streamingMode = 'whip';
|
|
1047
|
+
startWhip(endpointUrl);
|
|
1048
|
+
} else {
|
|
1049
|
+
console.error('Invalid protocol', error);
|
|
1050
|
+
errorHandler(error);
|
|
1051
|
+
}
|
|
812
1052
|
|
|
813
|
-
}
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
console.error('Cannot parse connection URL', error);
|
|
1055
|
+
errorHandler(error);
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
instance.stopStreaming = async function () {
|
|
1060
|
+
|
|
1061
|
+
if (instance.streamingMode === 'webrtc') {
|
|
1062
|
+
|
|
1063
|
+
instance.webSocketClosedByUser = true;
|
|
1064
|
+
|
|
1065
|
+
closeWebSocket();
|
|
1066
|
+
closePeerConnection();
|
|
1067
|
+
} else if (instance.streamingMode === 'whip') {
|
|
1068
|
+
|
|
1069
|
+
await stopWhip();
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
if (instance.callbacks.connectionClosed) {
|
|
1073
|
+
console.log(logHeader, 'Connection closed by user');
|
|
1074
|
+
instance.callbacks.connectionClosed('user', 'Connection closed by user');
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
instance.remove = function () {
|
|
1079
|
+
|
|
1080
|
+
if (instance.streamingMode === 'webrtc') {
|
|
1081
|
+
|
|
1082
|
+
instance.webSocketClosedByUser = true;
|
|
1083
|
+
|
|
1084
|
+
closeWebSocket();
|
|
1085
|
+
closePeerConnection();
|
|
1086
|
+
} else if (instance.streamingMode === 'whip') {
|
|
1087
|
+
stopWhip();
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
closeInputStream();
|
|
1091
|
+
|
|
1092
|
+
console.info(logEventHeader, 'Removed');
|
|
1093
|
+
|
|
1094
|
+
};
|
|
814
1095
|
}
|
|
815
1096
|
|
|
816
1097
|
OvenLiveKit.getVersion = function () {
|
|
817
|
-
|
|
1098
|
+
return version;
|
|
818
1099
|
}
|
|
819
1100
|
|
|
820
1101
|
// static methods
|
|
821
1102
|
OvenLiveKit.create = function (options) {
|
|
822
1103
|
|
|
823
|
-
|
|
1104
|
+
console.info(logEventHeader, 'Create WebRTC Input ' + version);
|
|
824
1105
|
|
|
825
|
-
|
|
1106
|
+
let instance = {};
|
|
826
1107
|
|
|
827
|
-
|
|
1108
|
+
instance.webSocketClosedByUser = false;
|
|
828
1109
|
|
|
829
|
-
|
|
830
|
-
|
|
1110
|
+
initConfig(instance, options);
|
|
1111
|
+
addMethod(instance);
|
|
831
1112
|
|
|
832
|
-
|
|
1113
|
+
return instance;
|
|
833
1114
|
};
|
|
834
1115
|
|
|
835
1116
|
OvenLiveKit.getDevices = async function () {
|
|
836
1117
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1118
|
+
try {
|
|
1119
|
+
// First check both audio and video sources are available.
|
|
1120
|
+
await getStreamForDeviceCheck('both');
|
|
1121
|
+
} catch (e) {
|
|
841
1122
|
|
|
842
|
-
|
|
1123
|
+
console.warn(logHeader, 'Can not find Video and Audio devices', e);
|
|
843
1124
|
|
|
844
|
-
|
|
845
|
-
|
|
1125
|
+
let videoFound = null;
|
|
1126
|
+
let audioFound = null;
|
|
846
1127
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1128
|
+
try {
|
|
1129
|
+
videoFound = await getStreamForDeviceCheck('video');
|
|
1130
|
+
} catch (e) {
|
|
1131
|
+
console.warn(logHeader, 'Can not find Video devices', e);
|
|
1132
|
+
}
|
|
852
1133
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1134
|
+
try {
|
|
1135
|
+
audioFound = await getStreamForDeviceCheck('audio');
|
|
1136
|
+
} catch (e) {
|
|
1137
|
+
console.warn(logHeader, 'Can not find Audio devices', e);
|
|
1138
|
+
}
|
|
858
1139
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}
|
|
1140
|
+
if (!videoFound && !audioFound) {
|
|
1141
|
+
throw new Error('No input devices were found.');
|
|
862
1142
|
}
|
|
1143
|
+
}
|
|
863
1144
|
|
|
864
|
-
|
|
865
|
-
|
|
1145
|
+
const deviceInfos = await getDevices();
|
|
1146
|
+
return gotDevices(deviceInfos)
|
|
866
1147
|
};
|
|
867
1148
|
|
|
868
1149
|
export default OvenLiveKit;
|