ovenlivekit 1.1.0 → 1.3.0

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