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