ovenlivekit 1.2.0 → 1.4.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,1177 @@
1
1
  const OvenLiveKit = {};
2
2
 
3
- const version = '1.1.0';
3
+ const version = '1.4.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);
59
+ const formatNumber = getFormatNumber(sdp, formatName);
60
60
 
61
- if (formatNumber === -1) {
62
- return sdp;
63
- }
61
+ if (formatNumber === -1) {
62
+ return sdp;
63
+ }
64
64
 
65
- let newLines = [];
66
- const lines = sdp.split('\r\n');
65
+ let newLines = [];
66
+ const lines = sdp.split('\r\n');
67
67
 
68
- for (let i = 0; i < lines.length - 1; i++) {
68
+ for (let i = 0; i < lines.length - 1; i++) {
69
69
 
70
- const line = lines[i];
70
+ const line = lines[i];
71
71
 
72
- if (line.indexOf('m=video') === 0) {
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
- 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
+ }
197
210
 
198
- if (!constraints) {
211
+ function getUserMedia(constraints) {
199
212
 
200
- constraints = {
201
- video: {
202
- deviceId: undefined
203
- },
204
- audio: {
205
- deviceId: undefined
206
- }
207
- };
213
+ if (!constraints) {
214
+
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;
277
+
278
+ let elem = instance.videoElement;
279
+
280
+ // Attach stream to video element when video element is provided.
281
+ if (elem) {
249
282
 
250
- if (!constraints) {
251
- constraints = {};
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) {
292
+
293
+ resolve(stream);
294
+ });
295
+ })
296
+ .catch(function (error) {
255
297
 
256
- return navigator.mediaDevices.getDisplayMedia(constraints)
257
- .then(function (stream) {
298
+ console.error(logHeader, 'Can\'t Get Media Stream From Display', error);
299
+ errorHandler(error);
258
300
 
259
- console.info(logHeader, 'Received Media Stream From Display', stream);
301
+ return new Promise(function (resolve, reject) {
302
+ reject(error);
303
+ });
304
+ });
305
+ }
260
306
 
261
- instance.inputStream = stream;
307
+ function setMediaStream(stream) {
308
+ // Check if a valid stream is provided
309
+ if (!stream || !(stream instanceof MediaStream)) {
262
310
 
263
- let elem = instance.videoElement;
311
+ const error = new Error("Invalid MediaStream provided");
312
+ console.error(logHeader, 'Invalid MediaStream', error);
313
+ errorHandler(error);
264
314
 
265
- // Attach stream to video element when video element is provided.
266
- if (elem) {
315
+ return new Promise(function (resolve, reject) {
316
+ reject(error);
317
+ });
318
+ }
267
319
 
268
- elem.srcObject = stream;
320
+ console.info(logHeader, 'Received Media Stream', stream);
269
321
 
270
- elem.onloadedmetadata = function (e) {
322
+ instance.inputStream = stream;
271
323
 
272
- elem.play();
273
- };
274
- }
324
+ let elem = instance.videoElement;
275
325
 
276
- return new Promise(function (resolve) {
326
+ // Attach stream to video element when video element is provided.
327
+ if (elem) {
328
+ elem.srcObject = stream;
277
329
 
278
- resolve(stream);
279
- });
280
- })
281
- .catch(function (error) {
330
+ elem.onloadedmetadata = function (e) {
331
+ elem.play();
332
+ };
333
+ }
282
334
 
283
- console.error(logHeader, 'Can\'t Get Media Stream From Display', error);
284
- errorHandler(error);
335
+ return new Promise(function (resolve) {
336
+ resolve(stream);
337
+ });
338
+ }
285
339
 
286
- return new Promise(function (resolve, reject) {
287
- reject(error);
288
- });
289
- });
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
+ }
458
+
459
+ const peerConnectionConfig = {
460
+ bundlePolicy: "max-bundle"
461
+ };
391
462
 
392
- // Request offer at the first time.
393
- sendMessage(webSocket, {
394
- command: 'request_offer'
395
- });
396
- };
463
+ if (instance.connectionConfig.iceServers) {
397
464
 
398
- webSocket.onmessage = function (e) {
465
+ // first priority using ice servers from local config.
466
+ peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;
399
467
 
400
- let message = JSON.parse(e.data);
468
+ if (instance.connectionConfig.iceTransportPolicy) {
401
469
 
402
- if (message.error) {
403
- console.error('webSocket.onmessage', message.error);
404
- errorHandler(message.error);
405
- }
470
+ peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;
471
+ }
472
+ } else {
473
+ // last priority using default ice servers.
406
474
 
407
- if (message.command === 'offer') {
475
+ if (instance.connectionConfig.iceTransportPolicy) {
408
476
 
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
- };
477
+ peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;
478
+ }
479
+ }
419
480
 
420
- webSocket.onerror = function (error) {
481
+ console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);
421
482
 
422
- console.error('webSocket.onerror', error);
423
- errorHandler(error);
424
- };
483
+ const peerConnection = new RTCPeerConnection(peerConnectionConfig);
425
484
 
426
- webSocket.onclose = function (e) {
485
+ instance.peerConnection = peerConnection;
427
486
 
428
- if (!instance.webSocketClosedByUser) {
487
+ if (!instance.inputStream) {
488
+ console.error('No input stream in OvenLiveKit');
489
+ errorHandler('No input stream in OvenLiveKit');
490
+ return;
491
+ }
429
492
 
430
- if (instance.callbacks.connectionClosed) {
431
- instance.callbacks.connectionClosed('websocket', e);
432
- }
433
- }
434
- };
493
+ for (const track of instance.inputStream.getTracks()) {
494
+ console.log(logHeader, 'Adding track: ', track);
495
+
496
+ const transceiverConfig = {
497
+ direction: 'sendonly'
498
+ };
499
+
500
+ // Add simulcast layers if configured
501
+ const simulcastConfig = instance.connectionConfig.simulcast;
435
502
 
503
+ if (track.kind === 'video' && simulcastConfig && simulcastConfig.length > 0) {
504
+
505
+ transceiverConfig.sendEncodings = [];
506
+
507
+ for (let i = 0; i < simulcastConfig.length; i++) {
508
+
509
+ const layer = {
510
+ rid: i,
511
+ active: true,
512
+ ...simulcastConfig[i]
513
+ };
514
+
515
+ console.log(logHeader, `Adding simulcast layer to: ${track.kind}`, layer);
516
+
517
+ transceiverConfig.sendEncodings.push(layer);
518
+ }
519
+ }
520
+
521
+ peerConnection.addTransceiver(track, transceiverConfig);
436
522
  }
437
523
 
438
- function appendFmtp(sdp) {
524
+ peerConnection.oniceconnectionstatechange = function (e) {
525
+
526
+ let state = peerConnection.iceConnectionState;
439
527
 
440
- const fmtpStr = instance.connectionConfig.sdp.appendFmtp;
528
+ if (instance.callbacks.iceStateChange) {
441
529
 
442
- const lines = sdp.split('\r\n');
443
- const payloads = [];
530
+ console.info(logHeader, 'ICE State', '[' + state + ']');
531
+ instance.callbacks.iceStateChange(state);
532
+ }
444
533
 
445
- for (let i = 0; i < lines.length; i++) {
534
+ if (state === 'connected') {
446
535
 
447
- if (lines[i].indexOf('m=video') === 0) {
536
+ if (instance.callbacks.connected) {
537
+ instance.callbacks.connected(e);
538
+ }
539
+ }
448
540
 
449
- let tokens = lines[i].split(' ')
541
+ if (state === 'failed') {
450
542
 
451
- for (let j = 3; j < tokens.length; j++) {
543
+ if (instance.callbacks.connectionClosed) {
544
+ console.error(logHeader, 'Ice connection failed', e);
545
+ instance.callbacks.errorHandler(e);
546
+ }
547
+ }
452
548
 
453
- payloads.push(tokens[j]);
454
- }
549
+ if (state === 'disconnected' || state === 'closed') {
455
550
 
456
- break;
457
- }
551
+ if (instance.callbacks.connectionClosed) {
552
+ console.error(logHeader, 'Ice connection disconnected or closed', e);
553
+ instance.callbacks.connectionClosed('ice', e);
458
554
  }
555
+ }
556
+ }
459
557
 
460
- for (let i = 0; i < payloads.length; i++) {
558
+ const offer = await peerConnection.createOffer();
559
+ console.log(logHeader, 'Offer SDP: ', offer.sdp);
461
560
 
462
- let fmtpLineFound = false;
561
+ if (instance.connectionConfig.maxVideoBitrate) {
463
562
 
464
- for (let j = 0; j < lines.length; j++) {
563
+ // if bandwidth limit is set. modify sdp from ome to limit acceptable bandwidth of ome
564
+ offer.sdp = setBitrateLimit(offer.sdp, 'video', instance.connectionConfig.maxVideoBitrate);
565
+ }
465
566
 
466
- if (lines[j].indexOf('a=fmtp:' + payloads[i]) === 0) {
467
- fmtpLineFound = true;
468
- lines[j] += ';' + fmtpStr;
469
- }
470
- }
567
+ if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {
568
+
569
+ offer.sdp = appendFmtp(offer.sdp);
570
+ }
471
571
 
472
- if (!fmtpLineFound) {
572
+ if (instance.connectionConfig.preferredVideoFormat) {
573
+ offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat);
574
+ } else {
575
+ // default to H264
576
+ offer.sdp = setPreferredVideoFormat(offer.sdp, 'H264');
577
+ }
473
578
 
474
- for (let j = 0; j < lines.length; j++) {
579
+ const headers = {
580
+ "Content-Type": "application/sdp"
581
+ };
475
582
 
476
- if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {
583
+ if (instance.connectionConfig.httpHeaders) {
584
+ Object.assign(headers, instance.connectionConfig.httpHeaders);
585
+ }
477
586
 
478
- lines[j] += '\r\na=fmtp:' + payloads[i] + ' ' + fmtpStr;
479
- }
480
- }
481
- }
587
+ const fetched = await fetchWithRedirect(endpointUrl, {
588
+ method: "POST",
589
+ body: offer.sdp,
590
+ headers
591
+ });
592
+
593
+ if (!fetched.ok) {
594
+ console.error('Failed to fetch', fetched.status);
595
+ errorHandler(`Failed to fetch ${fetched.status}`);
596
+ closePeerConnection();
597
+ return;
598
+ }
599
+
600
+ if (!fetched.headers.get("location")) {
601
+ console.error('No location header on answer response');
602
+ errorHandler('No location header on answer response');
603
+ return;
604
+ }
605
+
606
+ // update endpointUrl
607
+ instance.endpointUrl = fetched.url;
608
+ console.log(logHeader, 'Updated endpointUrl: ', instance.endpointUrl);
609
+
610
+ const baseUrl = new URL(endpointUrl).origin;
611
+ instance.resourceUrl = baseUrl + fetched.headers.get("location");
612
+
613
+ const answer = await fetched.text();
614
+ console.log(logHeader, 'Answer SDP: ', answer);
615
+
616
+ try {
617
+ await peerConnection.setLocalDescription(offer);
618
+ } catch (error) {
619
+ console.error('peerConnection.setLocalDescription', error);
620
+ errorHandler(error);
621
+ }
622
+
623
+ try {
624
+ await peerConnection.setRemoteDescription({
625
+ type: "answer",
626
+ sdp: answer
627
+ });
628
+ } catch (error) {
629
+ console.error('peerConnection.setRemoteDescription', error);
630
+ errorHandler(error);
631
+ }
632
+ }
633
+
634
+ async function stopWhip() {
635
+
636
+ if (!instance.peerConnection) {
637
+ console.error('No connection to close');
638
+ errorHandler('No connection to close');
639
+ return;
640
+ }
641
+
642
+ closePeerConnection();
643
+
644
+ if (instance.resourceUrl) {
645
+
646
+ const headers = {
647
+ };
648
+
649
+ if (instance.connectionConfig.httpHeaders) {
650
+ Object.assign(headers, instance.connectionConfig.httpHeaders);
651
+ }
652
+
653
+ await fetchWithRedirect(instance.resourceUrl, {
654
+ method: "DELETE",
655
+ headers
656
+ });
657
+ }
658
+ }
659
+
660
+ function appendFmtp(sdp) {
661
+
662
+ const fmtpStr = instance.connectionConfig.sdp.appendFmtp;
663
+
664
+ const lines = sdp.split('\r\n');
665
+ const payloads = [];
666
+
667
+ for (let i = 0; i < lines.length; i++) {
668
+
669
+ if (lines[i].indexOf('m=video') === 0) {
670
+
671
+ let tokens = lines[i].split(' ')
672
+
673
+ for (let j = 3; j < tokens.length; j++) {
674
+
675
+ payloads.push(tokens[j]);
482
676
  }
483
677
 
484
- return lines.join('\r\n')
678
+ break;
679
+ }
485
680
  }
486
681
 
487
- function appendOrientation(sdp) {
682
+ for (let i = 0; i < payloads.length; i++) {
488
683
 
489
- const lines = sdp.split('\r\n');
490
- const payloads = [];
684
+ let fmtpLineFound = false;
491
685
 
492
- for (let i = 0; i < lines.length; i++) {
686
+ for (let j = 0; j < lines.length; j++) {
493
687
 
494
- if (lines[i].indexOf('m=video') === 0) {
688
+ if (lines[j].indexOf('a=fmtp:' + payloads[i]) === 0) {
689
+ fmtpLineFound = true;
690
+ lines[j] += ';' + fmtpStr;
691
+ }
692
+ }
495
693
 
496
- let tokens = lines[i].split(' ')
694
+ if (!fmtpLineFound) {
497
695
 
498
- for (let j = 3; j < tokens.length; j++) {
696
+ for (let j = 0; j < lines.length; j++) {
499
697
 
500
- payloads.push(tokens[j]);
501
- }
698
+ if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {
502
699
 
503
- break;
504
- }
700
+ lines[j] += '\r\na=fmtp:' + payloads[i] + ' ' + fmtpStr;
701
+ }
505
702
  }
703
+ }
704
+ }
506
705
 
507
- for (let i = 0; i < payloads.length; i++) {
706
+ return lines.join('\r\n')
707
+ }
508
708
 
509
- for (let j = 0; j < lines.length; j++) {
709
+ function appendOrientation(sdp) {
510
710
 
511
- if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {
711
+ const lines = sdp.split('\r\n');
712
+ const payloads = [];
512
713
 
513
- lines[j] += '\r\na=extmap:' + payloads[i] + ' urn:3gpp:video-orientation';
514
- }
515
- }
714
+ for (let i = 0; i < lines.length; i++) {
715
+
716
+ if (lines[i].indexOf('m=video') === 0) {
717
+
718
+ let tokens = lines[i].split(' ')
719
+
720
+ for (let j = 3; j < tokens.length; j++) {
721
+
722
+ payloads.push(tokens[j]);
516
723
  }
517
724
 
518
- return lines.join('\r\n')
725
+ break;
726
+ }
519
727
  }
520
728
 
521
- function createPeerConnection(id, peerId, offer, candidates, iceServers) {
729
+ for (let i = 0; i < payloads.length; i++) {
522
730
 
523
- let peerConnectionConfig = {};
731
+ for (let j = 0; j < lines.length; j++) {
524
732
 
525
- if (instance.connectionConfig.iceServers) {
733
+ if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {
526
734
 
527
- // first priority using ice servers from local config.
528
- peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;
735
+ lines[j] += '\r\na=extmap:' + payloads[i] + ' urn:3gpp:video-orientation';
736
+ }
737
+ }
738
+ }
529
739
 
530
- if (instance.connectionConfig.iceTransportPolicy) {
740
+ return lines.join('\r\n')
741
+ }
531
742
 
532
- peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;
533
- }
534
- } else if (iceServers) {
743
+ function createPeerConnection(id, peerId, offer, candidates, iceServers) {
535
744
 
536
- // second priority using ice servers from ome and force using TCP
537
- peerConnectionConfig.iceServers = [];
745
+ let peerConnectionConfig = {};
538
746
 
539
- for (let i = 0; i < iceServers.length; i++) {
747
+ if (instance.connectionConfig.iceServers) {
540
748
 
541
- let iceServer = iceServers[i];
749
+ // first priority using ice servers from local config.
750
+ peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;
542
751
 
543
- let regIceServer = {};
752
+ if (instance.connectionConfig.iceTransportPolicy) {
544
753
 
545
- regIceServer.urls = iceServer.urls;
754
+ peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;
755
+ }
756
+ } else if (iceServers) {
546
757
 
547
- let hasWebSocketUrl = false;
548
- let webSocketUrl = generateDomainFromUrl(instance.connectionUrl);
758
+ // second priority using ice servers from ome and force using TCP
759
+ peerConnectionConfig.iceServers = [];
549
760
 
550
- for (let j = 0; j < regIceServer.urls.length; j++) {
761
+ for (let i = 0; i < iceServers.length; i++) {
551
762
 
552
- let serverUrl = regIceServer.urls[j];
763
+ let iceServer = iceServers[i];
553
764
 
554
- if (serverUrl.indexOf(webSocketUrl) > -1) {
555
- hasWebSocketUrl = true;
556
- break;
557
- }
558
- }
765
+ let regIceServer = {};
559
766
 
560
- if (!hasWebSocketUrl) {
767
+ regIceServer.urls = iceServer.urls;
561
768
 
562
- if (regIceServer.urls.length > 0) {
769
+ let hasWebSocketUrl = false;
770
+ let webSocketUrl = generateDomainFromUrl(instance.endpointUrl);
563
771
 
564
- let cloneIceServer = regIceServer.urls[0];
565
- let ip = findIp(cloneIceServer);
772
+ for (let j = 0; j < regIceServer.urls.length; j++) {
566
773
 
567
- if (webSocketUrl && ip) {
568
- regIceServer.urls.push(cloneIceServer.replace(ip, webSocketUrl));
569
- }
570
- }
571
- }
774
+ let serverUrl = regIceServer.urls[j];
572
775
 
573
- regIceServer.username = iceServer.user_name;
574
- regIceServer.credential = iceServer.credential;
776
+ if (serverUrl.indexOf(webSocketUrl) > -1) {
777
+ hasWebSocketUrl = true;
778
+ break;
779
+ }
780
+ }
575
781
 
576
- peerConnectionConfig.iceServers.push(regIceServer);
577
- }
782
+ if (!hasWebSocketUrl) {
578
783
 
579
- peerConnectionConfig.iceTransportPolicy = 'relay';
580
- } else {
581
- // last priority using default ice servers.
784
+ if (regIceServer.urls.length > 0) {
582
785
 
583
- if (instance.iceTransportPolicy) {
786
+ let cloneIceServer = regIceServer.urls[0];
787
+ let ip = findIp(cloneIceServer);
584
788
 
585
- peerConnectionConfig.iceTransportPolicy = instance.iceTransportPolicy;
789
+ if (webSocketUrl && ip) {
790
+ regIceServer.urls.push(cloneIceServer.replace(ip, webSocketUrl));
586
791
  }
792
+ }
587
793
  }
588
794
 
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
- });
795
+ regIceServer.username = iceServer.user_name;
796
+ regIceServer.credential = iceServer.credential;
626
797
 
627
- if (instance.connectionConfig.maxVideoBitrate) {
798
+ peerConnectionConfig.iceServers.push(regIceServer);
799
+ }
628
800
 
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
- }
801
+ if (instance.connectionConfig.iceTransportPolicy) {
802
+
803
+ peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;
804
+ } else {
805
+ peerConnectionConfig.iceTransportPolicy = 'relay';
806
+ }
807
+ } else {
808
+ // last priority using default ice servers.
632
809
 
633
- if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {
810
+ if (instance.connectionConfig.iceTransportPolicy) {
634
811
 
635
- offer.sdp = appendFmtp(offer.sdp);
636
- }
812
+ peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;
813
+ }
814
+ }
637
815
 
638
- if (instance.connectionConfig.preferredVideoFormat) {
639
- offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat)
816
+ let advancedSetting = {
817
+ optional: [
818
+ {
819
+ googHighStartBitrate: {
820
+ exact: !0
821
+ }
822
+ },
823
+ {
824
+ googPayloadPadding: {
825
+ exact: !0
826
+ }
827
+ },
828
+ {
829
+ googScreencastMinBitrate: {
830
+ exact: 500
831
+ }
832
+ },
833
+ {
834
+ enableDscp: {
835
+ exact: true
836
+ }
640
837
  }
838
+ ]
839
+ };
641
840
 
841
+ console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);
642
842
 
643
- // offer.sdp = appendOrientation(offer.sdp);
644
- console.info(logHeader, 'Modified offer sdp\n\n' + offer.sdp);
843
+ let peerConnection = new RTCPeerConnection(peerConnectionConfig);
645
844
 
646
- peerConnection.setRemoteDescription(new RTCSessionDescription(offer))
647
- .then(function () {
845
+ instance.peerConnection = peerConnection;
648
846
 
649
- peerConnection.createAnswer()
650
- .then(function (answer) {
847
+ // set local stream
848
+ instance.inputStream.getTracks().forEach(function (track) {
651
849
 
652
- if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {
850
+ console.info(logHeader, 'Add Track To Peer Connection', track);
851
+ peerConnection.addTrack(track, instance.inputStream);
852
+ });
653
853
 
654
- answer.sdp = appendFmtp(answer.sdp);
655
- }
854
+ if (instance.connectionConfig.maxVideoBitrate) {
855
+
856
+ // if bandwith limit is set. modify sdp from ome to limit acceptable bandwidth of ome
857
+ offer.sdp = setBitrateLimit(offer.sdp, 'video', instance.connectionConfig.maxVideoBitrate);
858
+ }
656
859
 
657
- if (instance.connectionConfig.preferredVideoFormat) {
658
- answer.sdp = setPreferredVideoFormat(answer.sdp, instance.connectionConfig.preferredVideoFormat)
659
- }
860
+ if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {
660
861
 
661
- console.info(logHeader, 'Modified answer sdp\n\n' + answer.sdp);
862
+ offer.sdp = appendFmtp(offer.sdp);
863
+ }
662
864
 
663
- peerConnection.setLocalDescription(answer)
664
- .then(function () {
865
+ if (instance.connectionConfig.preferredVideoFormat) {
866
+ offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat)
867
+ }
665
868
 
666
- sendMessage(instance.webSocket, {
667
- id: id,
668
- peer_id: peerId,
669
- command: 'answer',
670
- sdp: answer
671
- });
672
- })
673
- .catch(function (error) {
674
869
 
675
- console.error('peerConnection.setLocalDescription', error);
676
- errorHandler(error);
677
- });
678
- })
679
- .catch(function (error) {
870
+ // offer.sdp = appendOrientation(offer.sdp);
871
+ console.info(logHeader, 'Modified offer sdp\n\n' + offer.sdp);
680
872
 
681
- console.error('peerConnection.createAnswer', error);
682
- errorHandler(error);
683
- });
684
- })
685
- .catch(function (error) {
873
+ peerConnection.setRemoteDescription(new RTCSessionDescription(offer))
874
+ .then(function () {
686
875
 
687
- console.error('peerConnection.setRemoteDescription', error);
688
- errorHandler(error);
689
- });
876
+ peerConnection.createAnswer()
877
+ .then(function (answer) {
690
878
 
691
- if (candidates) {
879
+ if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {
692
880
 
693
- addIceCandidate(peerConnection, candidates);
694
- }
881
+ answer.sdp = appendFmtp(answer.sdp);
882
+ }
883
+
884
+ if (instance.connectionConfig.preferredVideoFormat) {
885
+ answer.sdp = setPreferredVideoFormat(answer.sdp, instance.connectionConfig.preferredVideoFormat)
886
+ }
695
887
 
696
- peerConnection.onicecandidate = function (e) {
888
+ console.info(logHeader, 'Modified answer sdp\n\n' + answer.sdp);
697
889
 
698
- if (e.candidate && e.candidate.candidate) {
890
+ peerConnection.setLocalDescription(answer)
891
+ .then(function () {
699
892
 
700
893
  sendMessage(instance.webSocket, {
701
- id: id,
702
- peer_id: peerId,
703
- command: 'candidate',
704
- candidates: [e.candidate]
894
+ id: id,
895
+ peer_id: peerId,
896
+ command: 'answer',
897
+ sdp: answer
705
898
  });
706
- }
707
- };
899
+ })
900
+ .catch(function (error) {
708
901
 
709
- peerConnection.oniceconnectionstatechange = function (e) {
902
+ console.error('peerConnection.setLocalDescription', error);
903
+ errorHandler(error);
904
+ });
905
+ })
906
+ .catch(function (error) {
710
907
 
711
- let state = peerConnection.iceConnectionState;
908
+ console.error('peerConnection.createAnswer', error);
909
+ errorHandler(error);
910
+ });
911
+ })
912
+ .catch(function (error) {
712
913
 
713
- if (instance.callbacks.iceStateChange) {
914
+ console.error('peerConnection.setRemoteDescription', error);
915
+ errorHandler(error);
916
+ });
714
917
 
715
- console.info(logHeader, 'ICE State', '[' + state + ']');
716
- instance.callbacks.iceStateChange(state);
717
- }
918
+ if (candidates) {
718
919
 
719
- if (state === 'connected') {
920
+ addIceCandidate(peerConnection, candidates);
921
+ }
720
922
 
721
- if (instance.callbacks.connected) {
722
- instance.callbacks.connected(e);
723
- }
724
- }
923
+ peerConnection.onicecandidate = function (e) {
725
924
 
726
- if (state === 'failed' || state === 'disconnected' || state === 'closed') {
925
+ if (e.candidate && e.candidate.candidate) {
727
926
 
728
- if (instance.callbacks.connectionClosed) {
729
- console.error(logHeader, 'Iceconnection Closed', e);
730
- instance.callbacks.connectionClosed('ice', e);
731
- }
732
- }
927
+ sendMessage(instance.webSocket, {
928
+ id: id,
929
+ peer_id: peerId,
930
+ command: 'candidate',
931
+ candidates: [e.candidate]
932
+ });
933
+ }
934
+ };
935
+
936
+ peerConnection.oniceconnectionstatechange = function (e) {
937
+
938
+ let state = peerConnection.iceConnectionState;
939
+
940
+ if (instance.callbacks.iceStateChange) {
941
+
942
+ console.info(logHeader, 'ICE State', '[' + state + ']');
943
+ instance.callbacks.iceStateChange(state);
944
+ }
945
+
946
+ if (state === 'connected') {
947
+
948
+ if (instance.callbacks.connected) {
949
+ instance.callbacks.connected(e);
950
+ }
951
+ }
952
+
953
+ if (state === 'failed' || state === 'disconnected' || state === 'closed') {
954
+
955
+ if (instance.callbacks.connectionClosed) {
956
+ console.error(logHeader, 'Ice connection closed', e);
957
+ instance.callbacks.connectionClosed('ice', e);
733
958
  }
959
+ }
734
960
  }
961
+ }
735
962
 
736
- function addIceCandidate(peerConnection, candidates) {
963
+ function addIceCandidate(peerConnection, candidates) {
737
964
 
738
- for (let i = 0; i < candidates.length; i++) {
965
+ for (let i = 0; i < candidates.length; i++) {
739
966
 
740
- if (candidates[i] && candidates[i].candidate) {
967
+ if (candidates[i] && candidates[i].candidate) {
741
968
 
742
- let basicCandidate = candidates[i];
969
+ let basicCandidate = candidates[i];
743
970
 
744
- peerConnection.addIceCandidate(new RTCIceCandidate(basicCandidate))
745
- .then(function () {
971
+ peerConnection.addIceCandidate(new RTCIceCandidate(basicCandidate))
972
+ .then(function () {
746
973
 
747
- })
748
- .catch(function (error) {
974
+ })
975
+ .catch(function (error) {
749
976
 
750
- console.error('peerConnection.addIceCandidate', error);
751
- errorHandler(error);
752
- });
753
- }
754
- }
977
+ console.error('peerConnection.addIceCandidate', error);
978
+ errorHandler(error);
979
+ });
980
+ }
755
981
  }
982
+ }
756
983
 
757
- function closePeerConnection() {
758
- if (instance.peerConnection) {
984
+ function closePeerConnection() {
985
+ if (instance.peerConnection) {
759
986
 
760
- // remove tracks from peer connection
761
- instance.peerConnection.getSenders().forEach(function (sender) {
762
- instance.peerConnection.removeTrack(sender);
763
- });
987
+ // remove tracks from peer connection
988
+ instance.peerConnection.getSenders().forEach(function (sender) {
989
+ instance.peerConnection.removeTrack(sender);
990
+ });
764
991
 
765
- instance.peerConnection.close();
766
- instance.peerConnection = null;
767
- delete instance.peerConnection;
768
- }
992
+ instance.peerConnection.close();
993
+ instance.peerConnection = null;
994
+ delete instance.peerConnection;
769
995
  }
996
+ }
770
997
 
771
- function closeWebSocket() {
998
+ function closeWebSocket() {
772
999
 
773
- if (instance.webSocket) {
1000
+ if (instance.webSocket) {
774
1001
 
775
- instance.webSocket.close();
776
- instance.webSocket = null;
777
- delete instance.webSocket;
778
- }
1002
+ instance.webSocket.close();
1003
+ instance.webSocket = null;
1004
+ delete instance.webSocket;
779
1005
  }
1006
+ }
780
1007
 
781
- function closeInputStream() {
782
- // release video, audio stream
783
- if (instance.inputStream) {
1008
+ function closeInputStream() {
1009
+ // release video, audio stream
1010
+ if (instance.inputStream) {
784
1011
 
785
- instance.inputStream.getTracks().forEach(track => {
1012
+ instance.inputStream.getTracks().forEach(track => {
786
1013
 
787
- track.stop();
788
- instance.inputStream.removeTrack(track);
789
- });
1014
+ track.stop();
1015
+ instance.inputStream.removeTrack(track);
1016
+ });
790
1017
 
791
- if (instance.videoElement) {
792
- instance.videoElement.srcObject = null;
793
- }
1018
+ if (instance.videoElement) {
1019
+ instance.videoElement.srcObject = null;
1020
+ }
794
1021
 
795
- instance.inputStream = null;
796
- delete instance.inputStream;
797
- }
1022
+ instance.inputStream = null;
1023
+ delete instance.inputStream;
798
1024
  }
1025
+ }
799
1026
 
800
- // instance methods
801
- instance.attachMedia = function (videoElement) {
1027
+ // instance methods
1028
+ instance.attachMedia = function (videoElement) {
802
1029
 
803
- instance.videoElement = videoElement;
804
- };
1030
+ instance.videoElement = videoElement;
1031
+ };
805
1032
 
806
- instance.getUserMedia = function (constraints) {
1033
+ instance.getUserMedia = function (constraints) {
807
1034
 
808
- return getUserMedia(constraints);
809
- };
1035
+ return getUserMedia(constraints);
1036
+ };
810
1037
 
811
- instance.getDisplayMedia = function (constraints) {
1038
+ instance.getDisplayMedia = function (constraints) {
812
1039
 
813
- return getDisplayMedia(constraints);
814
- };
1040
+ return getDisplayMedia(constraints);
1041
+ };
815
1042
 
816
- instance.setMediaStream = function (stream) {
1043
+ instance.setMediaStream = function (stream) {
817
1044
 
818
- return setMediaStream(stream);
819
- };
1045
+ return setMediaStream(stream);
1046
+ };
820
1047
 
821
- instance.startStreaming = function (connectionUrl, connectionConfig) {
1048
+ instance.startStreaming = function (endpointUrl, connectionConfig) {
822
1049
 
823
- console.info(logEventHeader, 'Start Streaming with connectionConfig', connectionConfig);
1050
+ console.info(logEventHeader, `Start Streaming to ${endpointUrl} with connectionConfig`, connectionConfig);
824
1051
 
825
- if (connectionConfig) {
1052
+ if (!endpointUrl) {
1053
+ console.error('endpointUrl is required');
1054
+ errorHandler('endpointUrl is required');
1055
+ return;
1056
+ }
826
1057
 
827
- instance.connectionConfig = connectionConfig;
828
- }
1058
+ instance.endpointUrl = endpointUrl;
829
1059
 
830
- initWebSocket(connectionUrl);
831
- };
1060
+ if (connectionConfig) {
1061
+ instance.connectionConfig = connectionConfig;
1062
+ }
1063
+
1064
+ try {
832
1065
 
833
- instance.stopStreaming = function () {
1066
+ const protocol = new URL(endpointUrl).protocol;
834
1067
 
835
- instance.webSocketClosedByUser = true;
1068
+ if (protocol === 'wss:' || protocol === 'ws:') {
836
1069
 
837
- closeWebSocket();
838
- closePeerConnection();
839
- };
1070
+ instance.streamingMode = 'webrtc';
1071
+ initWebSocket(endpointUrl);
1072
+ } else if (protocol === 'https:' || protocol === 'http:') {
840
1073
 
841
- instance.remove = function () {
1074
+ instance.streamingMode = 'whip';
1075
+ startWhip(endpointUrl);
1076
+ } else {
1077
+ console.error('Invalid protocol', error);
1078
+ errorHandler(error);
1079
+ }
842
1080
 
843
- instance.webSocketClosedByUser = true;
1081
+ } catch (error) {
1082
+ console.error('Cannot parse connection URL', error);
1083
+ errorHandler(error);
1084
+ }
1085
+ };
844
1086
 
845
- closeWebSocket();
846
- closePeerConnection();
847
- closeInputStream();
1087
+ instance.stopStreaming = async function () {
848
1088
 
849
- console.info(logEventHeader, 'Removed');
1089
+ if (instance.streamingMode === 'webrtc') {
850
1090
 
851
- };
1091
+ instance.webSocketClosedByUser = true;
1092
+
1093
+ closeWebSocket();
1094
+ closePeerConnection();
1095
+ } else if (instance.streamingMode === 'whip') {
1096
+
1097
+ await stopWhip();
1098
+ }
1099
+
1100
+ if (instance.callbacks.connectionClosed) {
1101
+ console.log(logHeader, 'Connection closed by user');
1102
+ instance.callbacks.connectionClosed('user', 'Connection closed by user');
1103
+ }
1104
+ };
1105
+
1106
+ instance.remove = function () {
1107
+
1108
+ if (instance.streamingMode === 'webrtc') {
1109
+
1110
+ instance.webSocketClosedByUser = true;
1111
+
1112
+ closeWebSocket();
1113
+ closePeerConnection();
1114
+ } else if (instance.streamingMode === 'whip') {
1115
+ stopWhip();
1116
+ }
1117
+
1118
+ closeInputStream();
1119
+
1120
+ console.info(logEventHeader, 'Removed');
1121
+
1122
+ };
852
1123
  }
853
1124
 
854
1125
  OvenLiveKit.getVersion = function () {
855
- return version;
1126
+ return version;
856
1127
  }
857
1128
 
858
1129
  // static methods
859
1130
  OvenLiveKit.create = function (options) {
860
1131
 
861
- console.info(logEventHeader, 'Create WebRTC Input ' + version);
1132
+ console.info(logEventHeader, 'Create WebRTC Input ' + version);
862
1133
 
863
- let instance = {};
1134
+ let instance = {};
864
1135
 
865
- instance.webSocketClosedByUser = false;
1136
+ instance.webSocketClosedByUser = false;
866
1137
 
867
- initConfig(instance, options);
868
- addMethod(instance);
1138
+ initConfig(instance, options);
1139
+ addMethod(instance);
869
1140
 
870
- return instance;
1141
+ return instance;
871
1142
  };
872
1143
 
873
1144
  OvenLiveKit.getDevices = async function () {
874
1145
 
875
- try {
876
- // First check both audio and video sources are available.
877
- await getStreamForDeviceCheck('both');
878
- } catch (e) {
1146
+ try {
1147
+ // First check both audio and video sources are available.
1148
+ await getStreamForDeviceCheck('both');
1149
+ } catch (e) {
879
1150
 
880
- console.warn(logHeader, 'Can not find Video and Audio devices', e);
1151
+ console.warn(logHeader, 'Can not find Video and Audio devices', e);
881
1152
 
882
- let videoFound = null;
883
- let audioFound = null;
1153
+ let videoFound = null;
1154
+ let audioFound = null;
884
1155
 
885
- try {
886
- videoFound = await getStreamForDeviceCheck('video');
887
- } catch (e) {
888
- console.warn(logHeader, 'Can not find Video devices', e);
889
- }
1156
+ try {
1157
+ videoFound = await getStreamForDeviceCheck('video');
1158
+ } catch (e) {
1159
+ console.warn(logHeader, 'Can not find Video devices', e);
1160
+ }
890
1161
 
891
- try {
892
- audioFound = await getStreamForDeviceCheck('audio');
893
- } catch (e) {
894
- console.warn(logHeader, 'Can not find Audio devices', e);
895
- }
1162
+ try {
1163
+ audioFound = await getStreamForDeviceCheck('audio');
1164
+ } catch (e) {
1165
+ console.warn(logHeader, 'Can not find Audio devices', e);
1166
+ }
896
1167
 
897
- if (!videoFound && !audioFound) {
898
- throw new Error('No input devices were found.');
899
- }
1168
+ if (!videoFound && !audioFound) {
1169
+ throw new Error('No input devices were found.');
900
1170
  }
1171
+ }
901
1172
 
902
- const deviceInfos = await getDevices();
903
- return gotDevices(deviceInfos)
1173
+ const deviceInfos = await getDevices();
1174
+ return gotDevices(deviceInfos)
904
1175
  };
905
1176
 
906
1177
  export default OvenLiveKit;