@gjsify/webrtc 0.3.13 → 0.3.14
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,70 +1,66 @@
|
|
|
1
1
|
import GLib from "gi://GLib?version=2.0";
|
|
2
|
+
|
|
3
|
+
//#region src/rtc-certificate.ts
|
|
2
4
|
const DEFAULT_EXPIRY_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
5
|
+
var RTCCertificate = class {
|
|
6
|
+
expires;
|
|
7
|
+
_fingerprints;
|
|
8
|
+
_algorithm;
|
|
9
|
+
/** @internal — use RTCPeerConnection.generateCertificate() */
|
|
10
|
+
constructor(algorithm, expires, fingerprints) {
|
|
11
|
+
this._algorithm = algorithm;
|
|
12
|
+
this.expires = expires;
|
|
13
|
+
this._fingerprints = fingerprints;
|
|
14
|
+
}
|
|
15
|
+
getFingerprints() {
|
|
16
|
+
return [...this._fingerprints];
|
|
17
|
+
}
|
|
18
|
+
/** @internal — the algorithm name for debugging/inspection */
|
|
19
|
+
get _algorithmName() {
|
|
20
|
+
return this._algorithm;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Generate a self-signed certificate for use with RTCPeerConnection.
|
|
25
|
+
* Supports ECDSA P-256 and RSASSA-PKCS1-v1_5 with SHA-256.
|
|
26
|
+
*
|
|
27
|
+
* The actual DTLS certificate used by webrtcbin is generated internally
|
|
28
|
+
* by GStreamer — this provides the spec-compliant JS API surface.
|
|
29
|
+
*/
|
|
22
30
|
async function generateCertificate(keygenAlgorithm) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
const uuid = GLib.uuid_string_random();
|
|
58
|
-
const checksum = GLib.Checksum.new(GLib.ChecksumType.SHA256);
|
|
59
|
-
checksum.update(new TextEncoder().encode(uuid + Date.now()));
|
|
60
|
-
const fingerprintHex = checksum.get_string();
|
|
61
|
-
const formatted = fingerprintHex.slice(0, 64).match(/.{2}/g).join(":").toUpperCase();
|
|
62
|
-
const expires = Date.now() + DEFAULT_EXPIRY_MS;
|
|
63
|
-
return new RTCCertificate(name, expires, [
|
|
64
|
-
{ algorithm: "sha-256", value: formatted }
|
|
65
|
-
]);
|
|
31
|
+
let name;
|
|
32
|
+
if (typeof keygenAlgorithm === "string") {
|
|
33
|
+
name = keygenAlgorithm.toLowerCase();
|
|
34
|
+
} else if (keygenAlgorithm && typeof keygenAlgorithm === "object" && typeof keygenAlgorithm.name === "string") {
|
|
35
|
+
name = keygenAlgorithm.name.toLowerCase();
|
|
36
|
+
} else {
|
|
37
|
+
throw new DOMException("generateCertificate: algorithm must have a name property", "NotSupportedError");
|
|
38
|
+
}
|
|
39
|
+
if (name === "ecdsa") {
|
|
40
|
+
const curve = keygenAlgorithm.namedCurve;
|
|
41
|
+
if (curve && curve !== "P-256") {
|
|
42
|
+
throw new DOMException(`generateCertificate: unsupported ECDSA curve '${curve}'`, "NotSupportedError");
|
|
43
|
+
}
|
|
44
|
+
} else if (name === "rsassa-pkcs1-v1_5") {
|
|
45
|
+
const hash = keygenAlgorithm.hash;
|
|
46
|
+
const hashName = typeof hash === "string" ? hash : hash?.name;
|
|
47
|
+
if (hashName && hashName.toUpperCase() === "SHA-1") {
|
|
48
|
+
throw new DOMException("generateCertificate: SHA-1 is not supported for RSA certificates", "NotSupportedError");
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
throw new DOMException(`generateCertificate: unsupported algorithm '${name}'`, "NotSupportedError");
|
|
52
|
+
}
|
|
53
|
+
const uuid = GLib.uuid_string_random();
|
|
54
|
+
const checksum = GLib.Checksum.new(GLib.ChecksumType.SHA256);
|
|
55
|
+
checksum.update(new TextEncoder().encode(uuid + Date.now()));
|
|
56
|
+
const fingerprintHex = checksum.get_string();
|
|
57
|
+
const formatted = fingerprintHex.slice(0, 64).match(/.{2}/g).join(":").toUpperCase();
|
|
58
|
+
const expires = Date.now() + DEFAULT_EXPIRY_MS;
|
|
59
|
+
return new RTCCertificate(name, expires, [{
|
|
60
|
+
algorithm: "sha-256",
|
|
61
|
+
value: formatted
|
|
62
|
+
}]);
|
|
66
63
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
};
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { RTCCertificate, generateCertificate };
|
|
@@ -1,261 +1,250 @@
|
|
|
1
|
+
import { RTCError } from "./rtc-error.js";
|
|
2
|
+
import { RTCErrorEvent } from "./rtc-events.js";
|
|
3
|
+
import { DOMException } from "@gjsify/dom-exception";
|
|
1
4
|
import GLib from "gi://GLib?version=2.0";
|
|
2
5
|
import { DataChannelBridge } from "@gjsify/webrtc-native";
|
|
3
|
-
import { DOMException } from "@gjsify/dom-exception";
|
|
4
6
|
import { Blob } from "@gjsify/buffer";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
|
|
8
|
+
//#region src/rtc-data-channel.ts
|
|
7
9
|
const STATE_MAP = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
1: "connecting",
|
|
11
|
+
2: "open",
|
|
12
|
+
3: "closing",
|
|
13
|
+
4: "closed"
|
|
12
14
|
};
|
|
15
|
+
/** Convert a JS typed array / ArrayBuffer to a GLib.Bytes. */
|
|
13
16
|
function toGBytes(buffer) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
let view;
|
|
18
|
+
if (ArrayBuffer.isView(buffer)) {
|
|
19
|
+
view = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
20
|
+
} else {
|
|
21
|
+
view = new Uint8Array(buffer);
|
|
22
|
+
}
|
|
23
|
+
return new GLib.Bytes(view);
|
|
21
24
|
}
|
|
25
|
+
/** Convert a GLib.Bytes payload to an ArrayBuffer. */
|
|
22
26
|
function bytesToArrayBuffer(bytes) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
class RTCDataChannel extends EventTarget {
|
|
34
|
-
_native;
|
|
35
|
-
_bridge;
|
|
36
|
-
_binaryType = "arraybuffer";
|
|
37
|
-
_bufferedAmount = 0;
|
|
38
|
-
_closed = false;
|
|
39
|
-
// `on<event>` attribute handlers — W3C requires both addEventListener and on*.
|
|
40
|
-
_onopen = null;
|
|
41
|
-
_onclose = null;
|
|
42
|
-
_onerror = null;
|
|
43
|
-
_onmessage = null;
|
|
44
|
-
_onbufferedamountlow = null;
|
|
45
|
-
_onclosing = null;
|
|
46
|
-
/**
|
|
47
|
-
* @internal
|
|
48
|
-
* Accepts either a raw GstWebRTCDataChannel (for locally-created channels)
|
|
49
|
-
* or a pre-made DataChannelBridge (for remotely-originated channels that
|
|
50
|
-
* the WebrtcbinBridge already wrapped on the streaming thread to avoid
|
|
51
|
-
* missing early messages).
|
|
52
|
-
*/
|
|
53
|
-
constructor(source) {
|
|
54
|
-
super();
|
|
55
|
-
if (source.channel !== void 0 && source.dispose_bridge) {
|
|
56
|
-
this._bridge = source;
|
|
57
|
-
this._native = this._bridge.channel;
|
|
58
|
-
} else {
|
|
59
|
-
this._native = source;
|
|
60
|
-
this._bridge = new DataChannelBridge({ channel: this._native });
|
|
61
|
-
}
|
|
62
|
-
this._bridge.connect("opened", () => this._handleOpen());
|
|
63
|
-
this._bridge.connect("closed", () => this._handleClose());
|
|
64
|
-
this._bridge.connect("error-occurred", (_b, message) => this._handleError(message));
|
|
65
|
-
this._bridge.connect("message-string", (_b, data) => this._handleString(data));
|
|
66
|
-
this._bridge.connect("message-data", (_b, data) => this._handleData(data));
|
|
67
|
-
this._bridge.connect("buffered-amount-low", () => this._handleBufferedAmountLow());
|
|
68
|
-
this._bridge.connect("ready-state-changed", () => this._handleReadyStateChange());
|
|
69
|
-
}
|
|
70
|
-
// ---- Properties --------------------------------------------------------
|
|
71
|
-
get label() {
|
|
72
|
-
return this._native.label;
|
|
73
|
-
}
|
|
74
|
-
get ordered() {
|
|
75
|
-
return this._native.ordered;
|
|
76
|
-
}
|
|
77
|
-
get protocol() {
|
|
78
|
-
return this._native.protocol;
|
|
79
|
-
}
|
|
80
|
-
get negotiated() {
|
|
81
|
-
return this._native.negotiated;
|
|
82
|
-
}
|
|
83
|
-
get id() {
|
|
84
|
-
return this._native.id >= 0 ? this._native.id : null;
|
|
85
|
-
}
|
|
86
|
-
get maxPacketLifeTime() {
|
|
87
|
-
const v = this._native.max_packet_lifetime;
|
|
88
|
-
return v >= 0 ? v : null;
|
|
89
|
-
}
|
|
90
|
-
get maxRetransmits() {
|
|
91
|
-
const v = this._native.max_retransmits;
|
|
92
|
-
return v >= 0 ? v : null;
|
|
93
|
-
}
|
|
94
|
-
get readyState() {
|
|
95
|
-
if (this._closed) return "closed";
|
|
96
|
-
return STATE_MAP[this._native.ready_state] ?? "connecting";
|
|
97
|
-
}
|
|
98
|
-
get bufferedAmount() {
|
|
99
|
-
try {
|
|
100
|
-
return Number(this._native.buffered_amount) || this._bufferedAmount;
|
|
101
|
-
} catch {
|
|
102
|
-
return this._bufferedAmount;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
get bufferedAmountLowThreshold() {
|
|
106
|
-
return Number(this._native.buffered_amount_low_threshold) || 0;
|
|
107
|
-
}
|
|
108
|
-
set bufferedAmountLowThreshold(v) {
|
|
109
|
-
this._native.buffered_amount_low_threshold = v;
|
|
110
|
-
}
|
|
111
|
-
get binaryType() {
|
|
112
|
-
return this._binaryType;
|
|
113
|
-
}
|
|
114
|
-
set binaryType(v) {
|
|
115
|
-
if (v !== "arraybuffer" && v !== "blob") return;
|
|
116
|
-
this._binaryType = v;
|
|
117
|
-
}
|
|
118
|
-
// ---- on<event> attribute accessors -------------------------------------
|
|
119
|
-
get onopen() {
|
|
120
|
-
return this._onopen;
|
|
121
|
-
}
|
|
122
|
-
set onopen(h) {
|
|
123
|
-
this._onopen = h;
|
|
124
|
-
}
|
|
125
|
-
get onclose() {
|
|
126
|
-
return this._onclose;
|
|
127
|
-
}
|
|
128
|
-
set onclose(h) {
|
|
129
|
-
this._onclose = h;
|
|
130
|
-
}
|
|
131
|
-
get onclosing() {
|
|
132
|
-
return this._onclosing;
|
|
133
|
-
}
|
|
134
|
-
set onclosing(h) {
|
|
135
|
-
this._onclosing = h;
|
|
136
|
-
}
|
|
137
|
-
get onerror() {
|
|
138
|
-
return this._onerror;
|
|
139
|
-
}
|
|
140
|
-
set onerror(h) {
|
|
141
|
-
this._onerror = h;
|
|
142
|
-
}
|
|
143
|
-
get onmessage() {
|
|
144
|
-
return this._onmessage;
|
|
145
|
-
}
|
|
146
|
-
set onmessage(h) {
|
|
147
|
-
this._onmessage = h;
|
|
148
|
-
}
|
|
149
|
-
get onbufferedamountlow() {
|
|
150
|
-
return this._onbufferedamountlow;
|
|
151
|
-
}
|
|
152
|
-
set onbufferedamountlow(h) {
|
|
153
|
-
this._onbufferedamountlow = h;
|
|
154
|
-
}
|
|
155
|
-
// ---- Methods -----------------------------------------------------------
|
|
156
|
-
send(data) {
|
|
157
|
-
const state = this.readyState;
|
|
158
|
-
if (state !== "open") {
|
|
159
|
-
throw new DOMException(
|
|
160
|
-
`RTCDataChannel.send: readyState is '${state}', expected 'open'`,
|
|
161
|
-
"InvalidStateError"
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
if (typeof data === "string") {
|
|
165
|
-
this._native.send_string(data);
|
|
166
|
-
this._bufferedAmount += new TextEncoder().encode(data).byteLength;
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
if (data instanceof Blob) {
|
|
170
|
-
const blob = data;
|
|
171
|
-
blob.arrayBuffer().then((buf) => {
|
|
172
|
-
try {
|
|
173
|
-
this._native.send_data(toGBytes(buf));
|
|
174
|
-
this._bufferedAmount += buf.byteLength;
|
|
175
|
-
} catch {
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
if (ArrayBuffer.isView(data)) {
|
|
181
|
-
const bytes = toGBytes(data);
|
|
182
|
-
this._native.send_data(bytes);
|
|
183
|
-
this._bufferedAmount += data.byteLength;
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (data instanceof ArrayBuffer) {
|
|
187
|
-
const bytes = toGBytes(data);
|
|
188
|
-
this._native.send_data(bytes);
|
|
189
|
-
this._bufferedAmount += data.byteLength;
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
throw new TypeError("RTCDataChannel.send: unsupported data type");
|
|
193
|
-
}
|
|
194
|
-
close() {
|
|
195
|
-
if (this._closed) return;
|
|
196
|
-
try {
|
|
197
|
-
this._native.close();
|
|
198
|
-
} catch {
|
|
199
|
-
}
|
|
200
|
-
this._disconnectSignals();
|
|
201
|
-
this._closed = true;
|
|
202
|
-
}
|
|
203
|
-
/** @internal */
|
|
204
|
-
_disconnectSignals() {
|
|
205
|
-
try {
|
|
206
|
-
this._bridge.dispose_bridge();
|
|
207
|
-
} catch {
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
// ---- Signal → event translators ---------------------------------------
|
|
211
|
-
// Already running on the main context (DataChannelBridge did the hop).
|
|
212
|
-
_handleOpen() {
|
|
213
|
-
const ev = new Event("open");
|
|
214
|
-
this._onopen?.call(this, ev);
|
|
215
|
-
this.dispatchEvent(ev);
|
|
216
|
-
}
|
|
217
|
-
_handleClose() {
|
|
218
|
-
this._closed = true;
|
|
219
|
-
const ev = new Event("close");
|
|
220
|
-
this._onclose?.call(this, ev);
|
|
221
|
-
this.dispatchEvent(ev);
|
|
222
|
-
}
|
|
223
|
-
_handleError(message) {
|
|
224
|
-
const rtcErr = new RTCError(
|
|
225
|
-
{ errorDetail: "data-channel-failure" },
|
|
226
|
-
message || "RTCDataChannel error"
|
|
227
|
-
);
|
|
228
|
-
const ev = new RTCErrorEvent("error", { error: rtcErr });
|
|
229
|
-
this._onerror?.call(this, ev);
|
|
230
|
-
this.dispatchEvent(ev);
|
|
231
|
-
}
|
|
232
|
-
_handleString(data) {
|
|
233
|
-
const ev = new MessageEvent("message", { data });
|
|
234
|
-
this._onmessage?.call(this, ev);
|
|
235
|
-
this.dispatchEvent(ev);
|
|
236
|
-
}
|
|
237
|
-
_handleData(bytes) {
|
|
238
|
-
if (!bytes) return;
|
|
239
|
-
const buf = bytesToArrayBuffer(bytes);
|
|
240
|
-
const data = this._binaryType === "blob" ? new Blob([buf]) : buf;
|
|
241
|
-
const ev = new MessageEvent("message", { data });
|
|
242
|
-
this._onmessage?.call(this, ev);
|
|
243
|
-
this.dispatchEvent(ev);
|
|
244
|
-
}
|
|
245
|
-
_handleBufferedAmountLow() {
|
|
246
|
-
this._bufferedAmount = Number(this._native.buffered_amount) || 0;
|
|
247
|
-
const ev = new Event("bufferedamountlow");
|
|
248
|
-
this._onbufferedamountlow?.call(this, ev);
|
|
249
|
-
this.dispatchEvent(ev);
|
|
250
|
-
}
|
|
251
|
-
_handleReadyStateChange() {
|
|
252
|
-
if (this.readyState === "closing") {
|
|
253
|
-
const ev = new Event("closing");
|
|
254
|
-
this._onclosing?.call(this, ev);
|
|
255
|
-
this.dispatchEvent(ev);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
27
|
+
const arr = bytes.toArray?.();
|
|
28
|
+
if (arr instanceof Uint8Array) {
|
|
29
|
+
return arr.buffer.slice(arr.byteOffset, arr.byteOffset + arr.byteLength);
|
|
30
|
+
}
|
|
31
|
+
const data = bytes.get_data?.();
|
|
32
|
+
if (data instanceof Uint8Array) {
|
|
33
|
+
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
34
|
+
}
|
|
35
|
+
return new ArrayBuffer(0);
|
|
258
36
|
}
|
|
259
|
-
|
|
260
|
-
|
|
37
|
+
var RTCDataChannel = class extends EventTarget {
|
|
38
|
+
_native;
|
|
39
|
+
_bridge;
|
|
40
|
+
_binaryType = "arraybuffer";
|
|
41
|
+
_bufferedAmount = 0;
|
|
42
|
+
_closed = false;
|
|
43
|
+
_onopen = null;
|
|
44
|
+
_onclose = null;
|
|
45
|
+
_onerror = null;
|
|
46
|
+
_onmessage = null;
|
|
47
|
+
_onbufferedamountlow = null;
|
|
48
|
+
_onclosing = null;
|
|
49
|
+
/**
|
|
50
|
+
* @internal
|
|
51
|
+
* Accepts either a raw GstWebRTCDataChannel (for locally-created channels)
|
|
52
|
+
* or a pre-made DataChannelBridge (for remotely-originated channels that
|
|
53
|
+
* the WebrtcbinBridge already wrapped on the streaming thread to avoid
|
|
54
|
+
* missing early messages).
|
|
55
|
+
*/
|
|
56
|
+
constructor(source) {
|
|
57
|
+
super();
|
|
58
|
+
if (source.channel !== undefined && source.dispose_bridge) {
|
|
59
|
+
this._bridge = source;
|
|
60
|
+
this._native = this._bridge.channel;
|
|
61
|
+
} else {
|
|
62
|
+
this._native = source;
|
|
63
|
+
this._bridge = new DataChannelBridge({ channel: this._native });
|
|
64
|
+
}
|
|
65
|
+
this._bridge.connect("opened", () => this._handleOpen());
|
|
66
|
+
this._bridge.connect("closed", () => this._handleClose());
|
|
67
|
+
this._bridge.connect("error-occurred", (_b, message) => this._handleError(message));
|
|
68
|
+
this._bridge.connect("message-string", (_b, data) => this._handleString(data));
|
|
69
|
+
this._bridge.connect("message-data", (_b, data) => this._handleData(data));
|
|
70
|
+
this._bridge.connect("buffered-amount-low", () => this._handleBufferedAmountLow());
|
|
71
|
+
this._bridge.connect("ready-state-changed", () => this._handleReadyStateChange());
|
|
72
|
+
}
|
|
73
|
+
get label() {
|
|
74
|
+
return this._native.label;
|
|
75
|
+
}
|
|
76
|
+
get ordered() {
|
|
77
|
+
return this._native.ordered;
|
|
78
|
+
}
|
|
79
|
+
get protocol() {
|
|
80
|
+
return this._native.protocol;
|
|
81
|
+
}
|
|
82
|
+
get negotiated() {
|
|
83
|
+
return this._native.negotiated;
|
|
84
|
+
}
|
|
85
|
+
get id() {
|
|
86
|
+
return this._native.id >= 0 ? this._native.id : null;
|
|
87
|
+
}
|
|
88
|
+
get maxPacketLifeTime() {
|
|
89
|
+
const v = this._native.max_packet_lifetime;
|
|
90
|
+
return v >= 0 ? v : null;
|
|
91
|
+
}
|
|
92
|
+
get maxRetransmits() {
|
|
93
|
+
const v = this._native.max_retransmits;
|
|
94
|
+
return v >= 0 ? v : null;
|
|
95
|
+
}
|
|
96
|
+
get readyState() {
|
|
97
|
+
if (this._closed) return "closed";
|
|
98
|
+
return STATE_MAP[this._native.ready_state] ?? "connecting";
|
|
99
|
+
}
|
|
100
|
+
get bufferedAmount() {
|
|
101
|
+
try {
|
|
102
|
+
return Number(this._native.buffered_amount) || this._bufferedAmount;
|
|
103
|
+
} catch {
|
|
104
|
+
return this._bufferedAmount;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
get bufferedAmountLowThreshold() {
|
|
108
|
+
return Number(this._native.buffered_amount_low_threshold) || 0;
|
|
109
|
+
}
|
|
110
|
+
set bufferedAmountLowThreshold(v) {
|
|
111
|
+
this._native.buffered_amount_low_threshold = v;
|
|
112
|
+
}
|
|
113
|
+
get binaryType() {
|
|
114
|
+
return this._binaryType;
|
|
115
|
+
}
|
|
116
|
+
set binaryType(v) {
|
|
117
|
+
if (v !== "arraybuffer" && v !== "blob") return;
|
|
118
|
+
this._binaryType = v;
|
|
119
|
+
}
|
|
120
|
+
get onopen() {
|
|
121
|
+
return this._onopen;
|
|
122
|
+
}
|
|
123
|
+
set onopen(h) {
|
|
124
|
+
this._onopen = h;
|
|
125
|
+
}
|
|
126
|
+
get onclose() {
|
|
127
|
+
return this._onclose;
|
|
128
|
+
}
|
|
129
|
+
set onclose(h) {
|
|
130
|
+
this._onclose = h;
|
|
131
|
+
}
|
|
132
|
+
get onclosing() {
|
|
133
|
+
return this._onclosing;
|
|
134
|
+
}
|
|
135
|
+
set onclosing(h) {
|
|
136
|
+
this._onclosing = h;
|
|
137
|
+
}
|
|
138
|
+
get onerror() {
|
|
139
|
+
return this._onerror;
|
|
140
|
+
}
|
|
141
|
+
set onerror(h) {
|
|
142
|
+
this._onerror = h;
|
|
143
|
+
}
|
|
144
|
+
get onmessage() {
|
|
145
|
+
return this._onmessage;
|
|
146
|
+
}
|
|
147
|
+
set onmessage(h) {
|
|
148
|
+
this._onmessage = h;
|
|
149
|
+
}
|
|
150
|
+
get onbufferedamountlow() {
|
|
151
|
+
return this._onbufferedamountlow;
|
|
152
|
+
}
|
|
153
|
+
set onbufferedamountlow(h) {
|
|
154
|
+
this._onbufferedamountlow = h;
|
|
155
|
+
}
|
|
156
|
+
send(data) {
|
|
157
|
+
const state = this.readyState;
|
|
158
|
+
if (state !== "open") {
|
|
159
|
+
throw new DOMException(`RTCDataChannel.send: readyState is '${state}', expected 'open'`, "InvalidStateError");
|
|
160
|
+
}
|
|
161
|
+
if (typeof data === "string") {
|
|
162
|
+
this._native.send_string(data);
|
|
163
|
+
this._bufferedAmount += new TextEncoder().encode(data).byteLength;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (data instanceof Blob) {
|
|
167
|
+
const blob = data;
|
|
168
|
+
blob.arrayBuffer().then((buf) => {
|
|
169
|
+
try {
|
|
170
|
+
this._native.send_data(toGBytes(buf));
|
|
171
|
+
this._bufferedAmount += buf.byteLength;
|
|
172
|
+
} catch {}
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (ArrayBuffer.isView(data)) {
|
|
177
|
+
const bytes = toGBytes(data);
|
|
178
|
+
this._native.send_data(bytes);
|
|
179
|
+
this._bufferedAmount += data.byteLength;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (data instanceof ArrayBuffer) {
|
|
183
|
+
const bytes = toGBytes(data);
|
|
184
|
+
this._native.send_data(bytes);
|
|
185
|
+
this._bufferedAmount += data.byteLength;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
throw new TypeError("RTCDataChannel.send: unsupported data type");
|
|
189
|
+
}
|
|
190
|
+
close() {
|
|
191
|
+
if (this._closed) return;
|
|
192
|
+
try {
|
|
193
|
+
this._native.close();
|
|
194
|
+
} catch {}
|
|
195
|
+
this._disconnectSignals();
|
|
196
|
+
this._closed = true;
|
|
197
|
+
}
|
|
198
|
+
/** @internal */
|
|
199
|
+
_disconnectSignals() {
|
|
200
|
+
try {
|
|
201
|
+
this._bridge.dispose_bridge();
|
|
202
|
+
} catch {}
|
|
203
|
+
}
|
|
204
|
+
_handleOpen() {
|
|
205
|
+
const ev = new Event("open");
|
|
206
|
+
this._onopen?.call(this, ev);
|
|
207
|
+
this.dispatchEvent(ev);
|
|
208
|
+
}
|
|
209
|
+
_handleClose() {
|
|
210
|
+
this._closed = true;
|
|
211
|
+
const ev = new Event("close");
|
|
212
|
+
this._onclose?.call(this, ev);
|
|
213
|
+
this.dispatchEvent(ev);
|
|
214
|
+
}
|
|
215
|
+
_handleError(message) {
|
|
216
|
+
const rtcErr = new RTCError({ errorDetail: "data-channel-failure" }, message || "RTCDataChannel error");
|
|
217
|
+
const ev = new RTCErrorEvent("error", { error: rtcErr });
|
|
218
|
+
this._onerror?.call(this, ev);
|
|
219
|
+
this.dispatchEvent(ev);
|
|
220
|
+
}
|
|
221
|
+
_handleString(data) {
|
|
222
|
+
const ev = new MessageEvent("message", { data });
|
|
223
|
+
this._onmessage?.call(this, ev);
|
|
224
|
+
this.dispatchEvent(ev);
|
|
225
|
+
}
|
|
226
|
+
_handleData(bytes) {
|
|
227
|
+
if (!bytes) return;
|
|
228
|
+
const buf = bytesToArrayBuffer(bytes);
|
|
229
|
+
const data = this._binaryType === "blob" ? new Blob([buf]) : buf;
|
|
230
|
+
const ev = new MessageEvent("message", { data });
|
|
231
|
+
this._onmessage?.call(this, ev);
|
|
232
|
+
this.dispatchEvent(ev);
|
|
233
|
+
}
|
|
234
|
+
_handleBufferedAmountLow() {
|
|
235
|
+
this._bufferedAmount = Number(this._native.buffered_amount) || 0;
|
|
236
|
+
const ev = new Event("bufferedamountlow");
|
|
237
|
+
this._onbufferedamountlow?.call(this, ev);
|
|
238
|
+
this.dispatchEvent(ev);
|
|
239
|
+
}
|
|
240
|
+
_handleReadyStateChange() {
|
|
241
|
+
if (this.readyState === "closing") {
|
|
242
|
+
const ev = new Event("closing");
|
|
243
|
+
this._onclosing?.call(this, ev);
|
|
244
|
+
this.dispatchEvent(ev);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
261
247
|
};
|
|
248
|
+
|
|
249
|
+
//#endregion
|
|
250
|
+
export { RTCDataChannel };
|