@gjsify/webrtc 0.3.13 → 0.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/get-user-media.js +95 -80
- package/lib/esm/gst-enum-maps.js +55 -59
- package/lib/esm/gst-init.js +19 -22
- package/lib/esm/gst-stats-parser.js +94 -67
- package/lib/esm/gst-utils.js +24 -13
- package/lib/esm/index.js +13 -43
- package/lib/esm/media-device-info.js +23 -22
- package/lib/esm/media-devices.js +150 -139
- package/lib/esm/media-stream-track.js +136 -139
- package/lib/esm/media-stream.js +76 -75
- package/lib/esm/register/data-channel.js +7 -3
- package/lib/esm/register/error.js +6 -2
- package/lib/esm/register/media-devices.js +6 -2
- package/lib/esm/register/media.js +8 -4
- package/lib/esm/register/peer-connection.js +9 -5
- package/lib/esm/rtc-certificate.js +62 -66
- package/lib/esm/rtc-data-channel.js +240 -251
- package/lib/esm/rtc-dtls-transport.js +40 -39
- package/lib/esm/rtc-dtmf-sender.js +92 -100
- package/lib/esm/rtc-error.js +24 -22
- package/lib/esm/rtc-events.js +33 -33
- package/lib/esm/rtc-ice-candidate.js +71 -72
- package/lib/esm/rtc-ice-transport.js +95 -94
- package/lib/esm/rtc-peer-connection.js +796 -845
- package/lib/esm/rtc-rtp-receiver.js +89 -87
- package/lib/esm/rtc-rtp-sender.js +282 -290
- package/lib/esm/rtc-rtp-transceiver.js +92 -93
- package/lib/esm/rtc-sctp-transport.js +38 -38
- package/lib/esm/rtc-session-description.js +47 -51
- package/lib/esm/rtc-stats-report.js +39 -34
- package/lib/esm/rtc-track-event.js +29 -27
- package/lib/esm/rtp-capabilities.js +81 -35
- package/lib/esm/tee-multiplexer.js +58 -60
- package/lib/esm/wpt-helpers.js +128 -112
- package/package.json +13 -13
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,861 +1,812 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
} from "@gjsify/webrtc-native";
|
|
7
|
-
import { ensureWebrtcbinAvailable, Gst } from "./gst-init.js";
|
|
1
|
+
import { Gst, ensureWebrtcbinAvailable } from "./gst-init.js";
|
|
2
|
+
import { MediaStreamTrack } from "./media-stream-track.js";
|
|
3
|
+
import { MediaStream } from "./media-stream.js";
|
|
4
|
+
import { gstToConnectionState, gstToIceConnectionState, gstToIceGatheringState, gstToSignalingState, w3cDirectionToGst } from "./gst-enum-maps.js";
|
|
5
|
+
import { filterStatsByTrackId, parseGstStats } from "./gst-stats-parser.js";
|
|
8
6
|
import { withGstPromise } from "./gst-utils.js";
|
|
9
|
-
import {
|
|
10
|
-
gstToSignalingState,
|
|
11
|
-
gstToConnectionState,
|
|
12
|
-
gstToIceConnectionState,
|
|
13
|
-
gstToIceGatheringState,
|
|
14
|
-
w3cDirectionToGst
|
|
15
|
-
} from "./gst-enum-maps.js";
|
|
16
|
-
import { DOMException } from "@gjsify/dom-exception";
|
|
17
7
|
import { RTCSessionDescription } from "./rtc-session-description.js";
|
|
18
8
|
import { RTCIceCandidate } from "./rtc-ice-candidate.js";
|
|
9
|
+
import { RTCDataChannelEvent, RTCPeerConnectionIceEvent } from "./rtc-events.js";
|
|
19
10
|
import { RTCDataChannel } from "./rtc-data-channel.js";
|
|
20
|
-
import { RTCPeerConnectionIceEvent, RTCDataChannelEvent } from "./rtc-events.js";
|
|
21
11
|
import { RTCRtpSender } from "./rtc-rtp-sender.js";
|
|
22
12
|
import { RTCRtpReceiver } from "./rtc-rtp-receiver.js";
|
|
23
13
|
import { RTCRtpTransceiver } from "./rtc-rtp-transceiver.js";
|
|
24
|
-
import { MediaStream } from "./media-stream.js";
|
|
25
|
-
import { MediaStreamTrack } from "./media-stream-track.js";
|
|
26
14
|
import { RTCTrackEvent } from "./rtc-track-event.js";
|
|
27
|
-
import { parseGstStats, filterStatsByTrackId } from "./gst-stats-parser.js";
|
|
28
15
|
import { RTCIceTransport } from "./rtc-ice-transport.js";
|
|
29
16
|
import { RTCDtlsTransport } from "./rtc-dtls-transport.js";
|
|
30
17
|
import { RTCSctpTransport } from "./rtc-sctp-transport.js";
|
|
31
18
|
import { RTCCertificate, generateCertificate } from "./rtc-certificate.js";
|
|
19
|
+
import { DOMException } from "@gjsify/dom-exception";
|
|
20
|
+
import GLib from "gi://GLib?version=2.0";
|
|
21
|
+
import GstWebRTC from "gi://GstWebRTC?version=1.0";
|
|
22
|
+
import { WebrtcbinBridge } from "@gjsify/webrtc-native";
|
|
23
|
+
import GObject from "gi://GObject?version=2.0";
|
|
24
|
+
|
|
25
|
+
//#region src/rtc-peer-connection.ts
|
|
26
|
+
/**
|
|
27
|
+
* Web-IDL `[EnforceRange] unsigned short` coercion. Coerces via ToNumber,
|
|
28
|
+
* rejects values that can't be represented as an unsigned short (0..65535).
|
|
29
|
+
* Matches Web-IDL §3.2.4.10: reject NaN, ±Infinity, and integers outside
|
|
30
|
+
* the range; "100" → 100; fractional values are truncated.
|
|
31
|
+
*
|
|
32
|
+
* Reference: refs/wpt/webrtc/RTCDataChannelInit-{maxPacketLifeTime,maxRetransmits}-enforce-range.html
|
|
33
|
+
*/
|
|
32
34
|
function coerceUnsignedShort(name, raw) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
const n = Number(raw);
|
|
36
|
+
if (!Number.isFinite(n)) {
|
|
37
|
+
throw new TypeError(`createDataChannel: ${name} must be a finite number, got ${String(raw)}`);
|
|
38
|
+
}
|
|
39
|
+
const truncated = Math.trunc(n);
|
|
40
|
+
if (truncated < 0 || truncated > 65535) {
|
|
41
|
+
throw new TypeError(`createDataChannel: ${name}=${truncated} is outside the [0, 65535] range`);
|
|
42
|
+
}
|
|
43
|
+
return truncated;
|
|
42
44
|
}
|
|
43
45
|
let globalCounter = 0;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
return this._ondatachannel;
|
|
808
|
-
}
|
|
809
|
-
set ondatachannel(v) {
|
|
810
|
-
this._ondatachannel = v;
|
|
811
|
-
}
|
|
812
|
-
get onicecandidate() {
|
|
813
|
-
return this._onicecandidate;
|
|
814
|
-
}
|
|
815
|
-
set onicecandidate(v) {
|
|
816
|
-
this._onicecandidate = v;
|
|
817
|
-
}
|
|
818
|
-
get oniceconnectionstatechange() {
|
|
819
|
-
return this._oniceconnectionstatechange;
|
|
820
|
-
}
|
|
821
|
-
set oniceconnectionstatechange(v) {
|
|
822
|
-
this._oniceconnectionstatechange = v;
|
|
823
|
-
}
|
|
824
|
-
get onicegatheringstatechange() {
|
|
825
|
-
return this._onicegatheringstatechange;
|
|
826
|
-
}
|
|
827
|
-
set onicegatheringstatechange(v) {
|
|
828
|
-
this._onicegatheringstatechange = v;
|
|
829
|
-
}
|
|
830
|
-
get onnegotiationneeded() {
|
|
831
|
-
return this._onnegotiationneeded;
|
|
832
|
-
}
|
|
833
|
-
set onnegotiationneeded(v) {
|
|
834
|
-
this._onnegotiationneeded = v;
|
|
835
|
-
}
|
|
836
|
-
get onsignalingstatechange() {
|
|
837
|
-
return this._onsignalingstatechange;
|
|
838
|
-
}
|
|
839
|
-
set onsignalingstatechange(v) {
|
|
840
|
-
this._onsignalingstatechange = v;
|
|
841
|
-
}
|
|
842
|
-
_ontrack = null;
|
|
843
|
-
get ontrack() {
|
|
844
|
-
return this._ontrack;
|
|
845
|
-
}
|
|
846
|
-
set ontrack(v) {
|
|
847
|
-
this._ontrack = v;
|
|
848
|
-
}
|
|
849
|
-
get onicecandidateerror() {
|
|
850
|
-
return null;
|
|
851
|
-
}
|
|
852
|
-
set onicecandidateerror(_v) {
|
|
853
|
-
}
|
|
854
|
-
// ---- Certificate management (Phase 4.7) --------------------------------
|
|
855
|
-
static generateCertificate(keygenAlgorithm) {
|
|
856
|
-
return generateCertificate(keygenAlgorithm);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
export {
|
|
860
|
-
RTCPeerConnection
|
|
46
|
+
var RTCPeerConnection = class extends EventTarget {
|
|
47
|
+
_pipeline;
|
|
48
|
+
_webrtcbin;
|
|
49
|
+
_bridge;
|
|
50
|
+
_conf;
|
|
51
|
+
_closed = false;
|
|
52
|
+
_iceRestartNeeded = false;
|
|
53
|
+
_hasNegotiated = false;
|
|
54
|
+
_dataChannels = new Map();
|
|
55
|
+
_transceivers = new Map();
|
|
56
|
+
_senders = [];
|
|
57
|
+
_receivers = [];
|
|
58
|
+
_iceTransport = null;
|
|
59
|
+
_dtlsTransport = null;
|
|
60
|
+
_sctpTransport = null;
|
|
61
|
+
canTrickleIceCandidates = true;
|
|
62
|
+
constructor(configuration) {
|
|
63
|
+
super();
|
|
64
|
+
ensureWebrtcbinAvailable();
|
|
65
|
+
const [major, minor] = Gst.version();
|
|
66
|
+
if (major < 1 || major === 1 && minor < 20) {
|
|
67
|
+
throw new DOMException(`@gjsify/webrtc requires GStreamer >= 1.20 (you have ${major}.${minor}). webrtcbin is only stable from 1.20 onward.`, "NotSupportedError");
|
|
68
|
+
}
|
|
69
|
+
const id = ++globalCounter;
|
|
70
|
+
this._pipeline = new Gst.Pipeline({ name: `gjsify-webrtc-pipeline-${id}` });
|
|
71
|
+
const bin = Gst.ElementFactory.make("webrtcbin", `gjsify-webrtcbin-${id}`);
|
|
72
|
+
if (!bin) {
|
|
73
|
+
throw new Error("Failed to create webrtcbin element");
|
|
74
|
+
}
|
|
75
|
+
this._webrtcbin = bin;
|
|
76
|
+
this._conf = { ...configuration };
|
|
77
|
+
if (configuration?.certificates) {
|
|
78
|
+
for (const cert of configuration.certificates) {
|
|
79
|
+
if (cert instanceof RTCCertificate && cert.expires <= Date.now()) {
|
|
80
|
+
throw new DOMException("RTCPeerConnection: one of the provided certificates has expired", "InvalidAccessError");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
this._applyIceServers(configuration?.iceServers ?? []);
|
|
85
|
+
this._applyIceTransportPolicy(configuration?.iceTransportPolicy);
|
|
86
|
+
this._applyBundlePolicy(configuration?.bundlePolicy);
|
|
87
|
+
this._pipeline.add(this._webrtcbin);
|
|
88
|
+
this._bridge = new WebrtcbinBridge({ bin: this._webrtcbin });
|
|
89
|
+
this._bridge.connect("negotiation-needed", () => this._handleNegotiationNeeded());
|
|
90
|
+
this._bridge.connect("icecandidate", (_b, mlineIndex, candidate) => this._handleIceCandidate(mlineIndex, candidate));
|
|
91
|
+
this._bridge.connect("datachannel", (_b, channelBridge) => this._handleDataChannel(channelBridge));
|
|
92
|
+
this._bridge.connect("new-transceiver", (_b, gstTrans) => this._handleNewTransceiver(gstTrans));
|
|
93
|
+
this._bridge.connect("pad-added", (_b, pad) => this._handlePadAdded(pad));
|
|
94
|
+
this._bridge.connect("connection-state-changed", () => this._dispatchStateChange("connectionstatechange"));
|
|
95
|
+
this._bridge.connect("ice-connection-state-changed", () => this._dispatchStateChange("iceconnectionstatechange"));
|
|
96
|
+
this._bridge.connect("ice-gathering-state-changed", () => this._dispatchStateChange("icegatheringstatechange"));
|
|
97
|
+
this._bridge.connect("signaling-state-changed", () => this._dispatchStateChange("signalingstatechange"));
|
|
98
|
+
this._pipeline.set_state(Gst.State.PLAYING);
|
|
99
|
+
}
|
|
100
|
+
_applyIceServers(iceServers) {
|
|
101
|
+
let stunSet = false;
|
|
102
|
+
for (const server of iceServers) {
|
|
103
|
+
const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
|
|
104
|
+
if (urls.length === 0) {
|
|
105
|
+
throw new SyntaxError("RTCIceServer.urls must not be empty");
|
|
106
|
+
}
|
|
107
|
+
for (const url of urls) {
|
|
108
|
+
if (typeof url !== "string" || url.length === 0) {
|
|
109
|
+
throw new TypeError("RTCIceServer.urls entries must be non-empty strings");
|
|
110
|
+
}
|
|
111
|
+
const colonIdx = url.indexOf(":");
|
|
112
|
+
if (colonIdx < 0) {
|
|
113
|
+
throw new TypeError(`Invalid ICE server URL "${url}"`);
|
|
114
|
+
}
|
|
115
|
+
const proto = url.slice(0, colonIdx + 1);
|
|
116
|
+
const hostPort = url.slice(colonIdx + 1);
|
|
117
|
+
if (proto === "stun:" || proto === "stuns:") {
|
|
118
|
+
if (stunSet) continue;
|
|
119
|
+
this._webrtcbin.stun_server = `${proto}//${hostPort}`;
|
|
120
|
+
stunSet = true;
|
|
121
|
+
} else if (proto === "turn:" || proto === "turns:") {
|
|
122
|
+
if (typeof server.username !== "string" || typeof server.credential !== "string") {
|
|
123
|
+
throw new TypeError(`TURN server credential for ${url} missing`);
|
|
124
|
+
}
|
|
125
|
+
const encUser = encodeURIComponent(server.username);
|
|
126
|
+
const encCred = encodeURIComponent(server.credential);
|
|
127
|
+
const turnUrl = `${proto}//${encUser}:${encCred}@${hostPort}`;
|
|
128
|
+
try {
|
|
129
|
+
this._webrtcbin.emit("add-turn-server", turnUrl);
|
|
130
|
+
} catch {
|
|
131
|
+
this._webrtcbin.turn_server = turnUrl;
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
throw new TypeError(`Unsupported ICE server protocol "${proto}"`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
_applyIceTransportPolicy(policy) {
|
|
140
|
+
if (!policy) return;
|
|
141
|
+
const gstPolicy = policy === "relay" ? GstWebRTC.WebRTCICETransportPolicy.RELAY : GstWebRTC.WebRTCICETransportPolicy.ALL;
|
|
142
|
+
try {
|
|
143
|
+
this._webrtcbin.ice_transport_policy = gstPolicy;
|
|
144
|
+
} catch {}
|
|
145
|
+
}
|
|
146
|
+
_applyBundlePolicy(policy) {
|
|
147
|
+
if (!policy) return;
|
|
148
|
+
let gstPolicy;
|
|
149
|
+
switch (policy) {
|
|
150
|
+
case "balanced":
|
|
151
|
+
gstPolicy = GstWebRTC.WebRTCBundlePolicy.BALANCED;
|
|
152
|
+
break;
|
|
153
|
+
case "max-compat":
|
|
154
|
+
gstPolicy = GstWebRTC.WebRTCBundlePolicy.MAX_COMPAT;
|
|
155
|
+
break;
|
|
156
|
+
case "max-bundle":
|
|
157
|
+
gstPolicy = GstWebRTC.WebRTCBundlePolicy.MAX_BUNDLE;
|
|
158
|
+
break;
|
|
159
|
+
default: return;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
this._webrtcbin.bundle_policy = gstPolicy;
|
|
163
|
+
} catch {}
|
|
164
|
+
}
|
|
165
|
+
get signalingState() {
|
|
166
|
+
if (this._closed) return "closed";
|
|
167
|
+
try {
|
|
168
|
+
return gstToSignalingState(this._webrtcbin.signaling_state);
|
|
169
|
+
} catch {
|
|
170
|
+
return "stable";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
get connectionState() {
|
|
174
|
+
if (this._closed) return "closed";
|
|
175
|
+
try {
|
|
176
|
+
return gstToConnectionState(this._webrtcbin.connection_state);
|
|
177
|
+
} catch {
|
|
178
|
+
return "new";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
get iceConnectionState() {
|
|
182
|
+
if (this._closed) return "closed";
|
|
183
|
+
try {
|
|
184
|
+
return gstToIceConnectionState(this._webrtcbin.ice_connection_state);
|
|
185
|
+
} catch {
|
|
186
|
+
return "new";
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
get iceGatheringState() {
|
|
190
|
+
try {
|
|
191
|
+
return gstToIceGatheringState(this._webrtcbin.ice_gathering_state);
|
|
192
|
+
} catch {
|
|
193
|
+
return "new";
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
_descProp(prop) {
|
|
197
|
+
try {
|
|
198
|
+
const desc = this._webrtcbin[prop];
|
|
199
|
+
if (!desc) return null;
|
|
200
|
+
return RTCSessionDescription.fromGstDesc(desc);
|
|
201
|
+
} catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
get localDescription() {
|
|
206
|
+
return this._descProp("local_description");
|
|
207
|
+
}
|
|
208
|
+
get remoteDescription() {
|
|
209
|
+
return this._descProp("remote_description");
|
|
210
|
+
}
|
|
211
|
+
get currentLocalDescription() {
|
|
212
|
+
return this._descProp("current_local_description");
|
|
213
|
+
}
|
|
214
|
+
get currentRemoteDescription() {
|
|
215
|
+
return this._descProp("current_remote_description");
|
|
216
|
+
}
|
|
217
|
+
get pendingLocalDescription() {
|
|
218
|
+
return this._descProp("pending_local_description");
|
|
219
|
+
}
|
|
220
|
+
get pendingRemoteDescription() {
|
|
221
|
+
return this._descProp("pending_remote_description");
|
|
222
|
+
}
|
|
223
|
+
get sctp() {
|
|
224
|
+
return this._sctpTransport;
|
|
225
|
+
}
|
|
226
|
+
get peerIdentity() {
|
|
227
|
+
return Promise.reject(new TypeError("peerIdentity assertions are not implemented"));
|
|
228
|
+
}
|
|
229
|
+
get idpErrorInfo() {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
get idpLoginUrl() {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
_rejectIfClosed(method) {
|
|
236
|
+
if (!this._closed) return;
|
|
237
|
+
throw new DOMException(`RTCPeerConnection.${method}: connection is closed`, "InvalidStateError");
|
|
238
|
+
}
|
|
239
|
+
async createOffer(_options) {
|
|
240
|
+
this._rejectIfClosed("createOffer");
|
|
241
|
+
const opts = Gst.Structure.new_empty("offer-options");
|
|
242
|
+
if (this._iceRestartNeeded) {
|
|
243
|
+
this._setStructureField(opts, "ice-restart", "boolean", true);
|
|
244
|
+
this._iceRestartNeeded = false;
|
|
245
|
+
}
|
|
246
|
+
const reply = await withGstPromise((p) => {
|
|
247
|
+
this._webrtcbin.emit("create-offer", opts, p);
|
|
248
|
+
});
|
|
249
|
+
const desc = reply.get_value("offer");
|
|
250
|
+
return RTCSessionDescription.fromGstDesc(desc).toJSON();
|
|
251
|
+
}
|
|
252
|
+
async createAnswer(_options) {
|
|
253
|
+
this._rejectIfClosed("createAnswer");
|
|
254
|
+
const opts = Gst.Structure.new_empty("answer-options");
|
|
255
|
+
const reply = await withGstPromise((p) => {
|
|
256
|
+
this._webrtcbin.emit("create-answer", opts, p);
|
|
257
|
+
});
|
|
258
|
+
const desc = reply.get_value("answer");
|
|
259
|
+
return RTCSessionDescription.fromGstDesc(desc).toJSON();
|
|
260
|
+
}
|
|
261
|
+
async setLocalDescription(description) {
|
|
262
|
+
this._rejectIfClosed("setLocalDescription");
|
|
263
|
+
if (!description || !description.type || !description.sdp) {
|
|
264
|
+
const state = this.signalingState;
|
|
265
|
+
if (state === "stable" || state === "have-local-offer") {
|
|
266
|
+
description = await this.createOffer();
|
|
267
|
+
} else if (state === "have-remote-offer" || state === "have-remote-pranswer") {
|
|
268
|
+
description = await this.createAnswer();
|
|
269
|
+
} else {
|
|
270
|
+
throw new DOMException(`setLocalDescription: cannot auto-create SDP in signalingState '${state}'`, "InvalidStateError");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
this._pipeline.set_state(Gst.State.PLAYING);
|
|
274
|
+
const gstDesc = new RTCSessionDescription(description).toGstDesc();
|
|
275
|
+
await withGstPromise((p) => {
|
|
276
|
+
this._webrtcbin.emit("set-local-description", gstDesc, p);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
async setRemoteDescription(description) {
|
|
280
|
+
this._rejectIfClosed("setRemoteDescription");
|
|
281
|
+
if (!description || !description.sdp || !description.type) {
|
|
282
|
+
throw new TypeError("setRemoteDescription requires an RTCSessionDescriptionInit with sdp and type");
|
|
283
|
+
}
|
|
284
|
+
this._pipeline.set_state(Gst.State.PLAYING);
|
|
285
|
+
const gstDesc = new RTCSessionDescription(description).toGstDesc();
|
|
286
|
+
await withGstPromise((p) => {
|
|
287
|
+
this._webrtcbin.emit("set-remote-description", gstDesc, p);
|
|
288
|
+
});
|
|
289
|
+
if (this.signalingState === "stable") {
|
|
290
|
+
this._hasNegotiated = true;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async addIceCandidate(candidate) {
|
|
294
|
+
this._rejectIfClosed("addIceCandidate");
|
|
295
|
+
if (!candidate) return;
|
|
296
|
+
const { candidate: cand, sdpMLineIndex } = candidate;
|
|
297
|
+
if (typeof cand !== "string" || typeof sdpMLineIndex !== "number") return;
|
|
298
|
+
this._webrtcbin.emit("add-ice-candidate", sdpMLineIndex, cand);
|
|
299
|
+
}
|
|
300
|
+
createDataChannel(label, options = {}) {
|
|
301
|
+
if (this._closed) {
|
|
302
|
+
throw new DOMException("Cannot create a data channel on a closed RTCPeerConnection", "InvalidStateError");
|
|
303
|
+
}
|
|
304
|
+
if (typeof label !== "string") {
|
|
305
|
+
throw new TypeError("createDataChannel: label must be a string");
|
|
306
|
+
}
|
|
307
|
+
if (new TextEncoder().encode(label).byteLength > 65535) {
|
|
308
|
+
throw new TypeError("createDataChannel: label too long (> 65535 bytes)");
|
|
309
|
+
}
|
|
310
|
+
const maxPacketLifeTime = options.maxPacketLifeTime == null ? undefined : coerceUnsignedShort("maxPacketLifeTime", options.maxPacketLifeTime);
|
|
311
|
+
const maxRetransmits = options.maxRetransmits == null ? undefined : coerceUnsignedShort("maxRetransmits", options.maxRetransmits);
|
|
312
|
+
const id = options.id == null ? undefined : coerceUnsignedShort("id", options.id);
|
|
313
|
+
if (maxPacketLifeTime !== undefined && maxRetransmits !== undefined) {
|
|
314
|
+
throw new TypeError("createDataChannel: maxPacketLifeTime and maxRetransmits are mutually exclusive");
|
|
315
|
+
}
|
|
316
|
+
if (options.negotiated === true && id === undefined) {
|
|
317
|
+
throw new TypeError("createDataChannel: negotiated=true requires an id");
|
|
318
|
+
}
|
|
319
|
+
if (id === 65535) {
|
|
320
|
+
throw new TypeError("createDataChannel: id 65535 is reserved");
|
|
321
|
+
}
|
|
322
|
+
const gstOpts = Gst.Structure.new_empty("data-channel-opts");
|
|
323
|
+
this._setStructureField(gstOpts, "ordered", "boolean", options.ordered);
|
|
324
|
+
this._setStructureField(gstOpts, "max-packet-lifetime", "int", maxPacketLifeTime);
|
|
325
|
+
this._setStructureField(gstOpts, "max-retransmits", "int", maxRetransmits);
|
|
326
|
+
this._setStructureField(gstOpts, "protocol", "string", options.protocol);
|
|
327
|
+
this._setStructureField(gstOpts, "negotiated", "boolean", options.negotiated);
|
|
328
|
+
this._setStructureField(gstOpts, "id", "int", id);
|
|
329
|
+
let native = null;
|
|
330
|
+
try {
|
|
331
|
+
native = this._webrtcbin.emit("create-data-channel", label, gstOpts);
|
|
332
|
+
} catch (err) {
|
|
333
|
+
throw new Error(`create-data-channel failed: ${err?.message ?? err}`);
|
|
334
|
+
}
|
|
335
|
+
if (!native) {
|
|
336
|
+
throw new Error("webrtcbin returned null data channel (check id/label/options)");
|
|
337
|
+
}
|
|
338
|
+
this._ensureSctpTransport();
|
|
339
|
+
const js = new RTCDataChannel(native);
|
|
340
|
+
this._dataChannels.set(native, js);
|
|
341
|
+
js.addEventListener("close", () => {
|
|
342
|
+
this._dataChannels.delete(native);
|
|
343
|
+
});
|
|
344
|
+
return js;
|
|
345
|
+
}
|
|
346
|
+
_setStructureField(structure, name, type, value) {
|
|
347
|
+
if (value == null) return;
|
|
348
|
+
const gvalue = new GObject.Value();
|
|
349
|
+
if (type === "boolean") {
|
|
350
|
+
gvalue.init(GObject.TYPE_BOOLEAN);
|
|
351
|
+
gvalue.set_boolean(Boolean(value));
|
|
352
|
+
} else if (type === "int") {
|
|
353
|
+
gvalue.init(GObject.TYPE_INT);
|
|
354
|
+
gvalue.set_int(Number(value));
|
|
355
|
+
} else if (type === "string") {
|
|
356
|
+
gvalue.init(GObject.TYPE_STRING);
|
|
357
|
+
gvalue.set_string(String(value));
|
|
358
|
+
}
|
|
359
|
+
structure.set_value(name, gvalue);
|
|
360
|
+
gvalue.unset();
|
|
361
|
+
}
|
|
362
|
+
getConfiguration() {
|
|
363
|
+
return { ...this._conf };
|
|
364
|
+
}
|
|
365
|
+
close() {
|
|
366
|
+
if (this._closed) return;
|
|
367
|
+
this._closed = true;
|
|
368
|
+
GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
|
|
369
|
+
try {
|
|
370
|
+
this._pipeline.set_state(Gst.State.NULL);
|
|
371
|
+
} catch {}
|
|
372
|
+
for (const ch of this._dataChannels.values()) {
|
|
373
|
+
try {
|
|
374
|
+
ch._disconnectSignals();
|
|
375
|
+
} catch {}
|
|
376
|
+
}
|
|
377
|
+
this._dataChannels.clear();
|
|
378
|
+
for (const s of this._senders) {
|
|
379
|
+
try {
|
|
380
|
+
s._teardownPipeline();
|
|
381
|
+
} catch {}
|
|
382
|
+
}
|
|
383
|
+
for (const r of this._receivers) {
|
|
384
|
+
try {
|
|
385
|
+
r._dispose();
|
|
386
|
+
} catch {}
|
|
387
|
+
}
|
|
388
|
+
this._transceivers.clear();
|
|
389
|
+
this._senders.length = 0;
|
|
390
|
+
this._receivers.length = 0;
|
|
391
|
+
if (this._dtlsTransport) this._dtlsTransport._setState("closed");
|
|
392
|
+
if (this._iceTransport) this._iceTransport._setState("closed");
|
|
393
|
+
if (this._sctpTransport) this._sctpTransport._setState("closed");
|
|
394
|
+
try {
|
|
395
|
+
this._bridge.dispose_bridge();
|
|
396
|
+
} catch {}
|
|
397
|
+
return GLib.SOURCE_REMOVE;
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
addTransceiver(trackOrKind, init) {
|
|
401
|
+
this._rejectIfClosed("addTransceiver");
|
|
402
|
+
let kind;
|
|
403
|
+
if (typeof trackOrKind === "string") {
|
|
404
|
+
if (trackOrKind !== "audio" && trackOrKind !== "video") {
|
|
405
|
+
throw new TypeError(`Failed to execute 'addTransceiver' on 'RTCPeerConnection': The provided value '${trackOrKind}' is not a valid enum value of type MediaStreamTrackKind.`);
|
|
406
|
+
}
|
|
407
|
+
kind = trackOrKind;
|
|
408
|
+
} else if (trackOrKind instanceof MediaStreamTrack) {
|
|
409
|
+
kind = trackOrKind.kind;
|
|
410
|
+
} else {
|
|
411
|
+
throw new TypeError("Failed to execute 'addTransceiver' on 'RTCPeerConnection': parameter 1 is not of type 'MediaStreamTrack' or a valid MediaStreamTrackKind.");
|
|
412
|
+
}
|
|
413
|
+
if (init?.sendEncodings) {
|
|
414
|
+
const rids = new Set();
|
|
415
|
+
for (const enc of init.sendEncodings) {
|
|
416
|
+
if (enc.rid !== undefined) {
|
|
417
|
+
if (typeof enc.rid !== "string" || enc.rid.length === 0 || enc.rid.length > 16 || !/^[a-zA-Z0-9]+$/.test(enc.rid)) {
|
|
418
|
+
throw new TypeError(`Invalid RID value: ${enc.rid}`);
|
|
419
|
+
}
|
|
420
|
+
if (rids.has(enc.rid)) {
|
|
421
|
+
throw new TypeError(`Duplicate RID: ${enc.rid}`);
|
|
422
|
+
}
|
|
423
|
+
rids.add(enc.rid);
|
|
424
|
+
}
|
|
425
|
+
if (enc.scaleResolutionDownBy !== undefined && enc.scaleResolutionDownBy < 1) {
|
|
426
|
+
throw new RangeError("scaleResolutionDownBy must be >= 1.0");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const direction = init?.direction ?? "sendrecv";
|
|
431
|
+
const validDirections = [
|
|
432
|
+
"sendrecv",
|
|
433
|
+
"sendonly",
|
|
434
|
+
"recvonly",
|
|
435
|
+
"inactive"
|
|
436
|
+
];
|
|
437
|
+
if (!validDirections.includes(direction)) {
|
|
438
|
+
throw new TypeError(`Failed to execute 'addTransceiver' on 'RTCPeerConnection': The provided value '${direction}' is not a valid enum value of type RTCRtpTransceiverDirection.`);
|
|
439
|
+
}
|
|
440
|
+
const hasGstSource = trackOrKind instanceof MediaStreamTrack && trackOrKind._gstSource;
|
|
441
|
+
const wantsSend = direction === "sendrecv" || direction === "sendonly";
|
|
442
|
+
let gstTrans;
|
|
443
|
+
let jsTrans;
|
|
444
|
+
if (hasGstSource && wantsSend) {
|
|
445
|
+
const track = trackOrKind;
|
|
446
|
+
const sender = new RTCRtpSender(null, this._pipeline, this._webrtcbin);
|
|
447
|
+
sender._kind = kind;
|
|
448
|
+
sender._onPipelineChanged = (newPipeline) => {
|
|
449
|
+
this._pipeline = newPipeline;
|
|
450
|
+
};
|
|
451
|
+
sender._setTrack(track);
|
|
452
|
+
sender._wirePipeline(track);
|
|
453
|
+
gstTrans = this._findNewGstTransceiver();
|
|
454
|
+
if (!gstTrans) {
|
|
455
|
+
throw new Error("webrtcbin did not create a transceiver for the send pad");
|
|
456
|
+
}
|
|
457
|
+
const gstReceiver = gstTrans.receiver ?? null;
|
|
458
|
+
const receiver = new RTCRtpReceiver(kind, gstReceiver, this._pipeline);
|
|
459
|
+
const statsDelegate = (t) => this.getStats(t);
|
|
460
|
+
sender._getStatsForTrack = statsDelegate;
|
|
461
|
+
receiver._getStatsForTrack = statsDelegate;
|
|
462
|
+
const dtls = this._ensureTransports();
|
|
463
|
+
sender._transport = dtls;
|
|
464
|
+
receiver._transport = dtls;
|
|
465
|
+
jsTrans = new RTCRtpTransceiver(gstTrans, sender, receiver);
|
|
466
|
+
sender._transceiver = jsTrans;
|
|
467
|
+
this._transceivers.set(gstTrans, jsTrans);
|
|
468
|
+
this._senders.push(sender);
|
|
469
|
+
this._receivers.push(receiver);
|
|
470
|
+
gstTrans.direction = w3cDirectionToGst(direction);
|
|
471
|
+
} else {
|
|
472
|
+
const caps = Gst.Caps.from_string(`application/x-rtp,media=${kind}`);
|
|
473
|
+
const createDirection = direction === "inactive" ? w3cDirectionToGst("sendrecv") : w3cDirectionToGst(direction);
|
|
474
|
+
gstTrans = this._webrtcbin.emit("add-transceiver", createDirection, caps);
|
|
475
|
+
if (!gstTrans) {
|
|
476
|
+
throw new Error("webrtcbin did not create a transceiver");
|
|
477
|
+
}
|
|
478
|
+
jsTrans = this._transceivers.get(gstTrans);
|
|
479
|
+
if (!jsTrans) {
|
|
480
|
+
jsTrans = this._createTransceiverWrapper(gstTrans);
|
|
481
|
+
}
|
|
482
|
+
gstTrans.direction = w3cDirectionToGst(direction);
|
|
483
|
+
if (trackOrKind instanceof MediaStreamTrack) {
|
|
484
|
+
jsTrans.sender._setTrack(trackOrKind);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return jsTrans;
|
|
488
|
+
}
|
|
489
|
+
addTrack(track, ..._streams) {
|
|
490
|
+
this._rejectIfClosed("addTrack");
|
|
491
|
+
if (!(track instanceof MediaStreamTrack)) {
|
|
492
|
+
throw new TypeError("Failed to execute 'addTrack' on 'RTCPeerConnection': parameter 1 is not a MediaStreamTrack");
|
|
493
|
+
}
|
|
494
|
+
const existing = this._senders.find((s) => s.track === track);
|
|
495
|
+
if (existing) {
|
|
496
|
+
throw new DOMException("Track already exists in a sender of this connection", "InvalidAccessError");
|
|
497
|
+
}
|
|
498
|
+
let reusable;
|
|
499
|
+
for (const t of this._transceivers.values()) {
|
|
500
|
+
if (t.sender.track === null && !t.stopped && t.direction !== "stopped" && t.receiver.track.kind === track.kind) {
|
|
501
|
+
const dir = t.direction;
|
|
502
|
+
if (dir === "recvonly" || dir === "inactive") {
|
|
503
|
+
reusable = t;
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (reusable) {
|
|
509
|
+
const dir = reusable.direction;
|
|
510
|
+
reusable.direction = dir === "recvonly" ? "sendrecv" : "sendonly";
|
|
511
|
+
reusable.sender._setTrack(track);
|
|
512
|
+
return reusable.sender;
|
|
513
|
+
}
|
|
514
|
+
const transceiver = this.addTransceiver(track, { direction: "sendrecv" });
|
|
515
|
+
return transceiver.sender;
|
|
516
|
+
}
|
|
517
|
+
removeTrack(sender) {
|
|
518
|
+
this._rejectIfClosed("removeTrack");
|
|
519
|
+
if (!this._senders.includes(sender)) {
|
|
520
|
+
throw new DOMException("sender was not created by this connection", "InvalidAccessError");
|
|
521
|
+
}
|
|
522
|
+
sender._setTrack(null);
|
|
523
|
+
}
|
|
524
|
+
getSenders() {
|
|
525
|
+
return [...this._senders];
|
|
526
|
+
}
|
|
527
|
+
getReceivers() {
|
|
528
|
+
return [...this._receivers];
|
|
529
|
+
}
|
|
530
|
+
getTransceivers() {
|
|
531
|
+
return [...this._transceivers.values()];
|
|
532
|
+
}
|
|
533
|
+
async getStats(selector) {
|
|
534
|
+
this._rejectIfClosed("getStats");
|
|
535
|
+
if (selector != null && selector instanceof MediaStreamTrack) {
|
|
536
|
+
const hasSender = this._senders.some((s) => s.track === selector);
|
|
537
|
+
const hasReceiver = this._receivers.some((r) => r.track === selector);
|
|
538
|
+
if (!hasSender && !hasReceiver) {
|
|
539
|
+
throw new DOMException("The selector track is not associated with a sender or receiver of this connection", "InvalidAccessError");
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const reply = await withGstPromise((p) => {
|
|
543
|
+
this._webrtcbin.emit("get-stats", null, p);
|
|
544
|
+
});
|
|
545
|
+
const report = parseGstStats(reply);
|
|
546
|
+
if (selector != null && selector instanceof MediaStreamTrack) {
|
|
547
|
+
return filterStatsByTrackId(report, selector.id);
|
|
548
|
+
}
|
|
549
|
+
return report;
|
|
550
|
+
}
|
|
551
|
+
restartIce() {
|
|
552
|
+
if (this._closed) return;
|
|
553
|
+
this._iceRestartNeeded = true;
|
|
554
|
+
if (this._hasNegotiated) {
|
|
555
|
+
Promise.resolve().then(() => {
|
|
556
|
+
if (this._closed) return;
|
|
557
|
+
this._handleNegotiationNeeded();
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
setConfiguration(configuration) {
|
|
562
|
+
this._rejectIfClosed("setConfiguration");
|
|
563
|
+
if (configuration.bundlePolicy && configuration.bundlePolicy !== (this._conf.bundlePolicy ?? "balanced")) {
|
|
564
|
+
throw new DOMException("setConfiguration: bundlePolicy cannot be changed", "InvalidModificationError");
|
|
565
|
+
}
|
|
566
|
+
if (configuration.rtcpMuxPolicy && configuration.rtcpMuxPolicy !== (this._conf.rtcpMuxPolicy ?? "require")) {
|
|
567
|
+
throw new DOMException("setConfiguration: rtcpMuxPolicy cannot be changed", "InvalidModificationError");
|
|
568
|
+
}
|
|
569
|
+
if (configuration.iceServers) {
|
|
570
|
+
this._applyIceServers(configuration.iceServers);
|
|
571
|
+
}
|
|
572
|
+
if (configuration.iceTransportPolicy) {
|
|
573
|
+
this._applyIceTransportPolicy(configuration.iceTransportPolicy);
|
|
574
|
+
}
|
|
575
|
+
this._conf = {
|
|
576
|
+
...this._conf,
|
|
577
|
+
...configuration
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
getIdentityAssertion() {
|
|
581
|
+
return Promise.reject(new Error("getIdentityAssertion is not implemented"));
|
|
582
|
+
}
|
|
583
|
+
/** Find a GstWebRTCRTPTransceiver not yet in our map (created by request_pad_simple). */
|
|
584
|
+
_findNewGstTransceiver() {
|
|
585
|
+
for (let i = 0;; i++) {
|
|
586
|
+
const gt = this._webrtcbin.emit("get-transceiver", i);
|
|
587
|
+
if (!gt) return null;
|
|
588
|
+
if (!this._transceivers.has(gt)) return gt;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/** Lazily create the shared DTLS and ICE transport instances (max-bundle → one pair). */
|
|
592
|
+
_ensureTransports() {
|
|
593
|
+
if (!this._dtlsTransport) {
|
|
594
|
+
this._iceTransport = new RTCIceTransport();
|
|
595
|
+
this._dtlsTransport = new RTCDtlsTransport(this._iceTransport);
|
|
596
|
+
}
|
|
597
|
+
return this._dtlsTransport;
|
|
598
|
+
}
|
|
599
|
+
/** Create the SCTP transport when a data channel is first negotiated. */
|
|
600
|
+
_ensureSctpTransport() {
|
|
601
|
+
if (this._sctpTransport) return;
|
|
602
|
+
const dtls = this._ensureTransports();
|
|
603
|
+
this._sctpTransport = new RTCSctpTransport(dtls);
|
|
604
|
+
}
|
|
605
|
+
_createTransceiverWrapper(gstTrans) {
|
|
606
|
+
let kind = "audio";
|
|
607
|
+
try {
|
|
608
|
+
const gstKind = gstTrans.kind;
|
|
609
|
+
if (gstKind === GstWebRTC.WebRTCKind.VIDEO) kind = "video";
|
|
610
|
+
} catch {}
|
|
611
|
+
const gstReceiver = gstTrans.receiver ?? null;
|
|
612
|
+
const gstSender = gstTrans.sender ?? null;
|
|
613
|
+
const receiver = new RTCRtpReceiver(kind, gstReceiver, this._pipeline);
|
|
614
|
+
const sender = new RTCRtpSender(gstSender, this._pipeline, this._webrtcbin);
|
|
615
|
+
sender._kind = kind;
|
|
616
|
+
sender._onPipelineChanged = (newPipeline) => {
|
|
617
|
+
this._pipeline = newPipeline;
|
|
618
|
+
};
|
|
619
|
+
const statsDelegate = (track) => this.getStats(track);
|
|
620
|
+
sender._getStatsForTrack = statsDelegate;
|
|
621
|
+
receiver._getStatsForTrack = statsDelegate;
|
|
622
|
+
const dtls = this._ensureTransports();
|
|
623
|
+
sender._transport = dtls;
|
|
624
|
+
receiver._transport = dtls;
|
|
625
|
+
try {
|
|
626
|
+
const mline = gstTrans.mlineindex;
|
|
627
|
+
if (typeof mline === "number" && mline >= 0) {
|
|
628
|
+
sender._setMlineIndex(mline);
|
|
629
|
+
}
|
|
630
|
+
} catch {}
|
|
631
|
+
const transceiver = new RTCRtpTransceiver(gstTrans, sender, receiver);
|
|
632
|
+
sender._transceiver = transceiver;
|
|
633
|
+
this._transceivers.set(gstTrans, transceiver);
|
|
634
|
+
this._senders.push(sender);
|
|
635
|
+
this._receivers.push(receiver);
|
|
636
|
+
return transceiver;
|
|
637
|
+
}
|
|
638
|
+
_handleNegotiationNeeded() {
|
|
639
|
+
const ev = new Event("negotiationneeded");
|
|
640
|
+
this._onnegotiationneeded?.call(this, ev);
|
|
641
|
+
this.dispatchEvent(ev);
|
|
642
|
+
}
|
|
643
|
+
_handleIceCandidate(sdpMLineIndex, candidate) {
|
|
644
|
+
const cand = new RTCIceCandidate({
|
|
645
|
+
candidate,
|
|
646
|
+
sdpMLineIndex
|
|
647
|
+
});
|
|
648
|
+
const ev = new RTCPeerConnectionIceEvent("icecandidate", { candidate: cand });
|
|
649
|
+
this._onicecandidate?.call(this, ev);
|
|
650
|
+
this.dispatchEvent(ev);
|
|
651
|
+
}
|
|
652
|
+
_handleNewTransceiver(gstTrans) {
|
|
653
|
+
if (this._closed) return;
|
|
654
|
+
if (this._transceivers.has(gstTrans)) return;
|
|
655
|
+
this._createTransceiverWrapper(gstTrans);
|
|
656
|
+
}
|
|
657
|
+
_handlePadAdded(pad) {
|
|
658
|
+
if (this._closed) return;
|
|
659
|
+
if (pad.direction !== Gst.PadDirection.SRC) return;
|
|
660
|
+
const gstTrans = pad.transceiver;
|
|
661
|
+
if (!gstTrans) return;
|
|
662
|
+
let jsTrans = this._transceivers.get(gstTrans);
|
|
663
|
+
if (!jsTrans) {
|
|
664
|
+
jsTrans = this._createTransceiverWrapper(gstTrans);
|
|
665
|
+
}
|
|
666
|
+
jsTrans.receiver._connectToPad(pad);
|
|
667
|
+
const stream = new MediaStream([jsTrans.receiver.track]);
|
|
668
|
+
const ev = new RTCTrackEvent("track", {
|
|
669
|
+
receiver: jsTrans.receiver,
|
|
670
|
+
track: jsTrans.receiver.track,
|
|
671
|
+
streams: [stream],
|
|
672
|
+
transceiver: jsTrans
|
|
673
|
+
});
|
|
674
|
+
this._ontrack?.call(this, ev);
|
|
675
|
+
this.dispatchEvent(ev);
|
|
676
|
+
}
|
|
677
|
+
_handleDataChannel(channelBridge) {
|
|
678
|
+
this._ensureSctpTransport();
|
|
679
|
+
const native = channelBridge.channel;
|
|
680
|
+
let js = this._dataChannels.get(native);
|
|
681
|
+
if (!js) {
|
|
682
|
+
js = new RTCDataChannel(channelBridge);
|
|
683
|
+
this._dataChannels.set(native, js);
|
|
684
|
+
js.addEventListener("close", () => {
|
|
685
|
+
this._dataChannels.delete(native);
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
const ev = new RTCDataChannelEvent("datachannel", { channel: js });
|
|
689
|
+
this._ondatachannel?.call(this, ev);
|
|
690
|
+
this.dispatchEvent(ev);
|
|
691
|
+
}
|
|
692
|
+
_dispatchStateChange(type) {
|
|
693
|
+
if (type === "connectionstatechange") {
|
|
694
|
+
this._syncDtlsState();
|
|
695
|
+
} else if (type === "iceconnectionstatechange") {
|
|
696
|
+
this._syncIceState();
|
|
697
|
+
} else if (type === "icegatheringstatechange") {
|
|
698
|
+
this._syncIceGatheringState();
|
|
699
|
+
}
|
|
700
|
+
const ev = new Event(type);
|
|
701
|
+
switch (type) {
|
|
702
|
+
case "connectionstatechange":
|
|
703
|
+
this._onconnectionstatechange?.call(this, ev);
|
|
704
|
+
break;
|
|
705
|
+
case "iceconnectionstatechange":
|
|
706
|
+
this._oniceconnectionstatechange?.call(this, ev);
|
|
707
|
+
break;
|
|
708
|
+
case "icegatheringstatechange":
|
|
709
|
+
this._onicegatheringstatechange?.call(this, ev);
|
|
710
|
+
break;
|
|
711
|
+
case "signalingstatechange":
|
|
712
|
+
this._onsignalingstatechange?.call(this, ev);
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
this.dispatchEvent(ev);
|
|
716
|
+
}
|
|
717
|
+
/** Map PC connection state → DTLS transport state. */
|
|
718
|
+
_syncDtlsState() {
|
|
719
|
+
if (!this._dtlsTransport) return;
|
|
720
|
+
const pcState = this.connectionState;
|
|
721
|
+
const dtlsMap = {
|
|
722
|
+
"new": "new",
|
|
723
|
+
"connecting": "connecting",
|
|
724
|
+
"connected": "connected",
|
|
725
|
+
"disconnected": "connected",
|
|
726
|
+
"failed": "failed",
|
|
727
|
+
"closed": "closed"
|
|
728
|
+
};
|
|
729
|
+
this._dtlsTransport._setState(dtlsMap[pcState] ?? "new");
|
|
730
|
+
if (pcState === "connected" && this._sctpTransport) {
|
|
731
|
+
this._sctpTransport._setState("connected");
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/** Map PC ICE connection state → ICE transport state. */
|
|
735
|
+
_syncIceState() {
|
|
736
|
+
if (!this._iceTransport) return;
|
|
737
|
+
const iceState = this.iceConnectionState;
|
|
738
|
+
this._iceTransport._setState(iceState);
|
|
739
|
+
}
|
|
740
|
+
/** Map PC ICE gathering state → ICE transport gathering state. */
|
|
741
|
+
_syncIceGatheringState() {
|
|
742
|
+
if (!this._iceTransport) return;
|
|
743
|
+
const gatheringState = this.iceGatheringState;
|
|
744
|
+
this._iceTransport._setGatheringState(gatheringState);
|
|
745
|
+
}
|
|
746
|
+
_onconnectionstatechange = null;
|
|
747
|
+
_ondatachannel = null;
|
|
748
|
+
_onicecandidate = null;
|
|
749
|
+
_oniceconnectionstatechange = null;
|
|
750
|
+
_onicegatheringstatechange = null;
|
|
751
|
+
_onnegotiationneeded = null;
|
|
752
|
+
_onsignalingstatechange = null;
|
|
753
|
+
get onconnectionstatechange() {
|
|
754
|
+
return this._onconnectionstatechange;
|
|
755
|
+
}
|
|
756
|
+
set onconnectionstatechange(v) {
|
|
757
|
+
this._onconnectionstatechange = v;
|
|
758
|
+
}
|
|
759
|
+
get ondatachannel() {
|
|
760
|
+
return this._ondatachannel;
|
|
761
|
+
}
|
|
762
|
+
set ondatachannel(v) {
|
|
763
|
+
this._ondatachannel = v;
|
|
764
|
+
}
|
|
765
|
+
get onicecandidate() {
|
|
766
|
+
return this._onicecandidate;
|
|
767
|
+
}
|
|
768
|
+
set onicecandidate(v) {
|
|
769
|
+
this._onicecandidate = v;
|
|
770
|
+
}
|
|
771
|
+
get oniceconnectionstatechange() {
|
|
772
|
+
return this._oniceconnectionstatechange;
|
|
773
|
+
}
|
|
774
|
+
set oniceconnectionstatechange(v) {
|
|
775
|
+
this._oniceconnectionstatechange = v;
|
|
776
|
+
}
|
|
777
|
+
get onicegatheringstatechange() {
|
|
778
|
+
return this._onicegatheringstatechange;
|
|
779
|
+
}
|
|
780
|
+
set onicegatheringstatechange(v) {
|
|
781
|
+
this._onicegatheringstatechange = v;
|
|
782
|
+
}
|
|
783
|
+
get onnegotiationneeded() {
|
|
784
|
+
return this._onnegotiationneeded;
|
|
785
|
+
}
|
|
786
|
+
set onnegotiationneeded(v) {
|
|
787
|
+
this._onnegotiationneeded = v;
|
|
788
|
+
}
|
|
789
|
+
get onsignalingstatechange() {
|
|
790
|
+
return this._onsignalingstatechange;
|
|
791
|
+
}
|
|
792
|
+
set onsignalingstatechange(v) {
|
|
793
|
+
this._onsignalingstatechange = v;
|
|
794
|
+
}
|
|
795
|
+
_ontrack = null;
|
|
796
|
+
get ontrack() {
|
|
797
|
+
return this._ontrack;
|
|
798
|
+
}
|
|
799
|
+
set ontrack(v) {
|
|
800
|
+
this._ontrack = v;
|
|
801
|
+
}
|
|
802
|
+
get onicecandidateerror() {
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
set onicecandidateerror(_v) {}
|
|
806
|
+
static generateCertificate(keygenAlgorithm) {
|
|
807
|
+
return generateCertificate(keygenAlgorithm);
|
|
808
|
+
}
|
|
861
809
|
};
|
|
810
|
+
|
|
811
|
+
//#endregion
|
|
812
|
+
export { RTCPeerConnection };
|