@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,93 +1,108 @@
|
|
|
1
1
|
import { Gst, ensureGstInit } from "./gst-init.js";
|
|
2
2
|
import { MediaStreamTrack } from "./media-stream-track.js";
|
|
3
3
|
import { MediaStream } from "./media-stream.js";
|
|
4
|
+
|
|
5
|
+
//#region src/get-user-media.ts
|
|
4
6
|
async function getUserMedia(constraints) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
7
|
+
ensureGstInit();
|
|
8
|
+
if (!constraints.audio && !constraints.video) {
|
|
9
|
+
throw new TypeError("Failed to execute 'getUserMedia': At least one of audio or video must be requested");
|
|
10
|
+
}
|
|
11
|
+
const tracks = [];
|
|
12
|
+
if (constraints.audio) {
|
|
13
|
+
const audioConstraints = typeof constraints.audio === "object" ? constraints.audio : {};
|
|
14
|
+
const source = _createAudioSource();
|
|
15
|
+
const pipeline = new Gst.Pipeline();
|
|
16
|
+
pipeline.add(source);
|
|
17
|
+
const capsStr = _buildAudioCaps(audioConstraints);
|
|
18
|
+
if (capsStr) {
|
|
19
|
+
const capsfilter = Gst.ElementFactory.make("capsfilter", null);
|
|
20
|
+
capsfilter.caps = Gst.Caps.from_string(capsStr);
|
|
21
|
+
pipeline.add(capsfilter);
|
|
22
|
+
source.link(capsfilter);
|
|
23
|
+
}
|
|
24
|
+
tracks.push(new MediaStreamTrack({
|
|
25
|
+
kind: "audio",
|
|
26
|
+
label: source.name ?? "audio",
|
|
27
|
+
_gst: {
|
|
28
|
+
source,
|
|
29
|
+
pipeline
|
|
30
|
+
}
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
if (constraints.video) {
|
|
34
|
+
const videoConstraints = typeof constraints.video === "object" ? constraints.video : {};
|
|
35
|
+
const source = _createVideoSource();
|
|
36
|
+
const pipeline = new Gst.Pipeline();
|
|
37
|
+
pipeline.add(source);
|
|
38
|
+
const capsStr = _buildVideoCaps(videoConstraints);
|
|
39
|
+
if (capsStr) {
|
|
40
|
+
const capsfilter = Gst.ElementFactory.make("capsfilter", null);
|
|
41
|
+
capsfilter.caps = Gst.Caps.from_string(capsStr);
|
|
42
|
+
pipeline.add(capsfilter);
|
|
43
|
+
source.link(capsfilter);
|
|
44
|
+
}
|
|
45
|
+
tracks.push(new MediaStreamTrack({
|
|
46
|
+
kind: "video",
|
|
47
|
+
label: source.name ?? "video",
|
|
48
|
+
_gst: {
|
|
49
|
+
source,
|
|
50
|
+
pipeline
|
|
51
|
+
}
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
return new MediaStream(tracks);
|
|
49
55
|
}
|
|
56
|
+
/** Build a GStreamer caps string for audio constraints. */
|
|
50
57
|
function _buildAudioCaps(c) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
const parts = [];
|
|
59
|
+
if (c.sampleRate != null) parts.push(`rate=${Math.trunc(c.sampleRate)}`);
|
|
60
|
+
if (c.channelCount != null) parts.push(`channels=${Math.trunc(c.channelCount)}`);
|
|
61
|
+
if (parts.length === 0) return null;
|
|
62
|
+
return `audio/x-raw,${parts.join(",")}`;
|
|
56
63
|
}
|
|
64
|
+
/** Build a GStreamer caps string for video constraints. */
|
|
57
65
|
function _buildVideoCaps(c) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
const parts = [];
|
|
67
|
+
if (c.width != null) parts.push(`width=${Math.trunc(c.width)}`);
|
|
68
|
+
if (c.height != null) parts.push(`height=${Math.trunc(c.height)}`);
|
|
69
|
+
if (c.frameRate != null) parts.push(`framerate=${Math.trunc(c.frameRate)}/1`);
|
|
70
|
+
if (parts.length === 0) return null;
|
|
71
|
+
return `video/x-raw,${parts.join(",")}`;
|
|
64
72
|
}
|
|
65
73
|
function _createAudioSource() {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
for (const name of [
|
|
75
|
+
"pipewiresrc",
|
|
76
|
+
"pulsesrc",
|
|
77
|
+
"autoaudiosrc"
|
|
78
|
+
]) {
|
|
79
|
+
const el = Gst.ElementFactory.make(name, null);
|
|
80
|
+
if (el) {
|
|
81
|
+
try {
|
|
82
|
+
el.is_live = true;
|
|
83
|
+
} catch {}
|
|
84
|
+
return el;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const el = Gst.ElementFactory.make("audiotestsrc", null);
|
|
88
|
+
el.is_live = true;
|
|
89
|
+
el.wave = 0;
|
|
90
|
+
return el;
|
|
80
91
|
}
|
|
81
92
|
function _createVideoSource() {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
for (const name of [
|
|
94
|
+
"pipewiresrc",
|
|
95
|
+
"v4l2src",
|
|
96
|
+
"autovideosrc"
|
|
97
|
+
]) {
|
|
98
|
+
const el = Gst.ElementFactory.make(name, null);
|
|
99
|
+
if (el) return el;
|
|
100
|
+
}
|
|
101
|
+
const el = Gst.ElementFactory.make("videotestsrc", null);
|
|
102
|
+
el.is_live = true;
|
|
103
|
+
el.pattern = 0;
|
|
104
|
+
return el;
|
|
90
105
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
};
|
|
106
|
+
|
|
107
|
+
//#endregion
|
|
108
|
+
export { getUserMedia };
|
package/lib/esm/gst-enum-maps.js
CHANGED
|
@@ -1,88 +1,84 @@
|
|
|
1
1
|
import GstWebRTC from "gi://GstWebRTC?version=1.0";
|
|
2
|
+
|
|
3
|
+
//#region src/gst-enum-maps.ts
|
|
2
4
|
const SIGNALING_STATE_MAP = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
[GstWebRTC.WebRTCSignalingState.STABLE]: "stable",
|
|
6
|
+
[GstWebRTC.WebRTCSignalingState.CLOSED]: "closed",
|
|
7
|
+
[GstWebRTC.WebRTCSignalingState.HAVE_LOCAL_OFFER]: "have-local-offer",
|
|
8
|
+
[GstWebRTC.WebRTCSignalingState.HAVE_REMOTE_OFFER]: "have-remote-offer",
|
|
9
|
+
[GstWebRTC.WebRTCSignalingState.HAVE_LOCAL_PRANSWER]: "have-local-pranswer",
|
|
10
|
+
[GstWebRTC.WebRTCSignalingState.HAVE_REMOTE_PRANSWER]: "have-remote-pranswer"
|
|
9
11
|
};
|
|
10
12
|
function gstToSignalingState(v) {
|
|
11
|
-
|
|
13
|
+
return SIGNALING_STATE_MAP[v] ?? "stable";
|
|
12
14
|
}
|
|
13
15
|
const CONNECTION_STATE_MAP = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
[GstWebRTC.WebRTCPeerConnectionState.NEW]: "new",
|
|
17
|
+
[GstWebRTC.WebRTCPeerConnectionState.CONNECTING]: "connecting",
|
|
18
|
+
[GstWebRTC.WebRTCPeerConnectionState.CONNECTED]: "connected",
|
|
19
|
+
[GstWebRTC.WebRTCPeerConnectionState.DISCONNECTED]: "disconnected",
|
|
20
|
+
[GstWebRTC.WebRTCPeerConnectionState.FAILED]: "failed",
|
|
21
|
+
[GstWebRTC.WebRTCPeerConnectionState.CLOSED]: "closed"
|
|
20
22
|
};
|
|
21
23
|
function gstToConnectionState(v) {
|
|
22
|
-
|
|
24
|
+
return CONNECTION_STATE_MAP[v] ?? "new";
|
|
23
25
|
}
|
|
24
26
|
const ICE_CONNECTION_STATE_MAP = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
[GstWebRTC.WebRTCICEConnectionState.NEW]: "new",
|
|
28
|
+
[GstWebRTC.WebRTCICEConnectionState.CHECKING]: "checking",
|
|
29
|
+
[GstWebRTC.WebRTCICEConnectionState.CONNECTED]: "connected",
|
|
30
|
+
[GstWebRTC.WebRTCICEConnectionState.COMPLETED]: "completed",
|
|
31
|
+
[GstWebRTC.WebRTCICEConnectionState.FAILED]: "failed",
|
|
32
|
+
[GstWebRTC.WebRTCICEConnectionState.DISCONNECTED]: "disconnected",
|
|
33
|
+
[GstWebRTC.WebRTCICEConnectionState.CLOSED]: "closed"
|
|
32
34
|
};
|
|
33
35
|
function gstToIceConnectionState(v) {
|
|
34
|
-
|
|
36
|
+
return ICE_CONNECTION_STATE_MAP[v] ?? "new";
|
|
35
37
|
}
|
|
36
38
|
const ICE_GATHERING_STATE_MAP = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
[GstWebRTC.WebRTCICEGatheringState.NEW]: "new",
|
|
40
|
+
[GstWebRTC.WebRTCICEGatheringState.GATHERING]: "gathering",
|
|
41
|
+
[GstWebRTC.WebRTCICEGatheringState.COMPLETE]: "complete"
|
|
40
42
|
};
|
|
41
43
|
function gstToIceGatheringState(v) {
|
|
42
|
-
|
|
44
|
+
return ICE_GATHERING_STATE_MAP[v] ?? "new";
|
|
43
45
|
}
|
|
44
46
|
const DIRECTION_GST_TO_W3C = {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
[GstWebRTC.WebRTCRTPTransceiverDirection.SENDRECV]: "sendrecv",
|
|
48
|
+
[GstWebRTC.WebRTCRTPTransceiverDirection.SENDONLY]: "sendonly",
|
|
49
|
+
[GstWebRTC.WebRTCRTPTransceiverDirection.RECVONLY]: "recvonly"
|
|
48
50
|
};
|
|
49
51
|
const DIRECTION_W3C_TO_GST = {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
sendrecv: GstWebRTC.WebRTCRTPTransceiverDirection.SENDRECV,
|
|
53
|
+
sendonly: GstWebRTC.WebRTCRTPTransceiverDirection.SENDONLY,
|
|
54
|
+
recvonly: GstWebRTC.WebRTCRTPTransceiverDirection.RECVONLY,
|
|
55
|
+
inactive: GstWebRTC.WebRTCRTPTransceiverDirection.NONE
|
|
54
56
|
};
|
|
55
57
|
function gstDirectionToW3C(v) {
|
|
56
|
-
|
|
58
|
+
return DIRECTION_GST_TO_W3C[v] ?? "inactive";
|
|
57
59
|
}
|
|
58
60
|
function w3cDirectionToGst(d) {
|
|
59
|
-
|
|
61
|
+
return DIRECTION_W3C_TO_GST[d] ?? GstWebRTC.WebRTCRTPTransceiverDirection.NONE;
|
|
60
62
|
}
|
|
61
63
|
const STATS_TYPE_MAP = {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
64
|
+
[GstWebRTC.WebRTCStatsType.CODEC]: "codec",
|
|
65
|
+
[GstWebRTC.WebRTCStatsType.INBOUND_RTP]: "inbound-rtp",
|
|
66
|
+
[GstWebRTC.WebRTCStatsType.OUTBOUND_RTP]: "outbound-rtp",
|
|
67
|
+
[GstWebRTC.WebRTCStatsType.REMOTE_INBOUND_RTP]: "remote-inbound-rtp",
|
|
68
|
+
[GstWebRTC.WebRTCStatsType.REMOTE_OUTBOUND_RTP]: "remote-outbound-rtp",
|
|
69
|
+
[GstWebRTC.WebRTCStatsType.CSRC]: "csrc",
|
|
70
|
+
[GstWebRTC.WebRTCStatsType.PEER_CONNECTION]: "peer-connection",
|
|
71
|
+
[GstWebRTC.WebRTCStatsType.DATA_CHANNEL]: "data-channel",
|
|
72
|
+
[GstWebRTC.WebRTCStatsType.STREAM]: "stream",
|
|
73
|
+
[GstWebRTC.WebRTCStatsType.TRANSPORT]: "transport",
|
|
74
|
+
[GstWebRTC.WebRTCStatsType.CANDIDATE_PAIR]: "candidate-pair",
|
|
75
|
+
[GstWebRTC.WebRTCStatsType.LOCAL_CANDIDATE]: "local-candidate",
|
|
76
|
+
[GstWebRTC.WebRTCStatsType.REMOTE_CANDIDATE]: "remote-candidate",
|
|
77
|
+
[GstWebRTC.WebRTCStatsType.CERTIFICATE]: "certificate"
|
|
76
78
|
};
|
|
77
79
|
function gstToStatsType(v) {
|
|
78
|
-
|
|
80
|
+
return STATS_TYPE_MAP[v];
|
|
79
81
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
gstToIceConnectionState,
|
|
84
|
-
gstToIceGatheringState,
|
|
85
|
-
gstToSignalingState,
|
|
86
|
-
gstToStatsType,
|
|
87
|
-
w3cDirectionToGst
|
|
88
|
-
};
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
export { gstDirectionToW3C, gstToConnectionState, gstToIceConnectionState, gstToIceGatheringState, gstToSignalingState, gstToStatsType, w3cDirectionToGst };
|
package/lib/esm/gst-init.js
CHANGED
|
@@ -1,31 +1,28 @@
|
|
|
1
1
|
import Gst from "gi://Gst?version=1.0";
|
|
2
2
|
import { DOMException } from "@gjsify/dom-exception";
|
|
3
|
+
|
|
4
|
+
//#region src/gst-init.ts
|
|
3
5
|
let initialized = false;
|
|
4
6
|
function ensureGstInit() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
if (initialized) return;
|
|
8
|
+
Gst.init(null);
|
|
9
|
+
initialized = true;
|
|
8
10
|
}
|
|
11
|
+
/** Throws if the `webrtcbin` element is not registered (gst-plugins-bad missing). */
|
|
9
12
|
function ensureWebrtcbinAvailable() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
throwNotSupported(
|
|
20
|
-
'GStreamer "nice" plugin (libnice-gstreamer) not available \u2014 required by webrtcbin.\n Fedora: dnf install libnice-gstreamer1\n Ubuntu/Debian: apt install gstreamer1.0-nice\n Verify with: gst-inspect-1.0 nicesrc'
|
|
21
|
-
);
|
|
22
|
-
}
|
|
13
|
+
ensureGstInit();
|
|
14
|
+
const webrtcFactory = Gst.ElementFactory.find("webrtcbin");
|
|
15
|
+
if (!webrtcFactory) {
|
|
16
|
+
throwNotSupported("GStreamer element \"webrtcbin\" not available. Install gst-plugins-bad:\n" + " Fedora: dnf install gstreamer1-plugins-bad-free gstreamer1-plugins-bad-free-extras\n" + " Ubuntu/Debian: apt install gstreamer1.0-plugins-bad");
|
|
17
|
+
}
|
|
18
|
+
const niceFactory = Gst.ElementFactory.find("nicesrc");
|
|
19
|
+
if (!niceFactory) {
|
|
20
|
+
throwNotSupported("GStreamer \"nice\" plugin (libnice-gstreamer) not available — required by webrtcbin.\n" + " Fedora: dnf install libnice-gstreamer1\n" + " Ubuntu/Debian: apt install gstreamer1.0-nice\n" + " Verify with: gst-inspect-1.0 nicesrc");
|
|
21
|
+
}
|
|
23
22
|
}
|
|
24
23
|
function throwNotSupported(message) {
|
|
25
|
-
|
|
24
|
+
throw new DOMException(message, "NotSupportedError");
|
|
26
25
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
ensureWebrtcbinAvailable
|
|
31
|
-
};
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { Gst, ensureGstInit, ensureWebrtcbinAvailable };
|
|
@@ -1,79 +1,106 @@
|
|
|
1
1
|
import { gstToStatsType } from "./gst-enum-maps.js";
|
|
2
2
|
import { RTCStatsReport } from "./rtc-stats-report.js";
|
|
3
|
+
|
|
4
|
+
//#region src/gst-stats-parser.ts
|
|
5
|
+
/**
|
|
6
|
+
* Convert a GStreamer snake_case field name to W3C camelCase.
|
|
7
|
+
* Examples: `bytes-sent` → `bytesSent`, `packets-received` → `packetsReceived`
|
|
8
|
+
*/
|
|
3
9
|
function snakeToCamel(name) {
|
|
4
|
-
|
|
10
|
+
return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
5
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Extract all fields from a GstStructure into a plain object.
|
|
14
|
+
* GJS unboxes `get_value` for boxed/simple types directly — no
|
|
15
|
+
* GObject.Value wrapper is returned for primitive types.
|
|
16
|
+
*/
|
|
6
17
|
function extractFields(structure) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return result;
|
|
18
|
+
const result = {};
|
|
19
|
+
const n = structure.n_fields();
|
|
20
|
+
for (let i = 0; i < n; i++) {
|
|
21
|
+
const fieldName = structure.nth_field_name(i);
|
|
22
|
+
try {
|
|
23
|
+
const value = structure.get_value(fieldName);
|
|
24
|
+
result[fieldName] = value;
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
18
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse a single stats entry (nested GstStructure) into a W3C RTCStats object.
|
|
31
|
+
*/
|
|
19
32
|
function parseStatsEntry(structure) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const raw = extractFields(structure);
|
|
34
|
+
const gstType = raw["type"];
|
|
35
|
+
if (gstType == null) return null;
|
|
36
|
+
const w3cType = gstToStatsType(Number(gstType));
|
|
37
|
+
if (!w3cType) return null;
|
|
38
|
+
const id = String(raw["id"] ?? structure.get_name() ?? "");
|
|
39
|
+
const tsRaw = raw["timestamp"];
|
|
40
|
+
const timestamp = tsRaw != null ? Number(tsRaw) / 1e6 : performance.now();
|
|
41
|
+
const stats = {
|
|
42
|
+
type: w3cType,
|
|
43
|
+
id,
|
|
44
|
+
timestamp
|
|
45
|
+
};
|
|
46
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
47
|
+
if (key === "type" || key === "id" || key === "timestamp") continue;
|
|
48
|
+
const camelKey = snakeToCamel(key);
|
|
49
|
+
stats[camelKey] = value;
|
|
50
|
+
}
|
|
51
|
+
return stats;
|
|
35
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Parse the top-level GstStructure returned by webrtcbin's `get-stats`
|
|
55
|
+
* signal into a W3C RTCStatsReport.
|
|
56
|
+
*
|
|
57
|
+
* The top-level structure has one field per stats entry. Each field's value
|
|
58
|
+
* is a nested GstStructure (GJS unboxes boxed types automatically).
|
|
59
|
+
*/
|
|
36
60
|
function parseGstStats(reply) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return new RTCStatsReport(entries);
|
|
61
|
+
if (!reply) return new RTCStatsReport();
|
|
62
|
+
const entries = [];
|
|
63
|
+
const n = reply.n_fields();
|
|
64
|
+
for (let i = 0; i < n; i++) {
|
|
65
|
+
const fieldName = reply.nth_field_name(i);
|
|
66
|
+
try {
|
|
67
|
+
const nested = reply.get_value(fieldName);
|
|
68
|
+
if (nested && typeof nested.n_fields === "function") {
|
|
69
|
+
const stats = parseStatsEntry(nested);
|
|
70
|
+
if (stats) {
|
|
71
|
+
entries.push([stats.id, stats]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch {}
|
|
75
|
+
}
|
|
76
|
+
return new RTCStatsReport(entries);
|
|
54
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Filter an RTCStatsReport to entries relevant to a specific track identifier.
|
|
80
|
+
* Used by RTCRtpSender.getStats() and RTCRtpReceiver.getStats() to return
|
|
81
|
+
* only the stats associated with their track.
|
|
82
|
+
*/
|
|
55
83
|
function filterStatsByTrackId(report, trackId) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
const entries = [];
|
|
85
|
+
const relatedIds = new Set();
|
|
86
|
+
for (const [id, stats] of report) {
|
|
87
|
+
if (stats.trackIdentifier === trackId) {
|
|
88
|
+
entries.push([id, stats]);
|
|
89
|
+
relatedIds.add(id);
|
|
90
|
+
for (const value of Object.values(stats)) {
|
|
91
|
+
if (typeof value === "string" && report.has(value)) {
|
|
92
|
+
relatedIds.add(value);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const [id, stats] of report) {
|
|
98
|
+
if (relatedIds.has(id) && !entries.some(([entryId]) => entryId === id)) {
|
|
99
|
+
entries.push([id, stats]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return new RTCStatsReport(entries);
|
|
75
103
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
};
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
export { filterStatsByTrackId, parseGstStats };
|
package/lib/esm/gst-utils.js
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { PromiseBridge } from "@gjsify/webrtc-native";
|
|
2
|
+
|
|
3
|
+
//#region src/gst-utils.ts
|
|
4
|
+
/**
|
|
5
|
+
* Wrap a GstPromise-consuming emit into a JS Promise that resolves on the
|
|
6
|
+
* main thread.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const reply = await withGstPromise((promise) => {
|
|
10
|
+
* webrtcbin.emit('create-offer', options, promise);
|
|
11
|
+
* });
|
|
12
|
+
*/
|
|
2
13
|
function withGstPromise(emit) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const bridge = new PromiseBridge();
|
|
16
|
+
bridge.connect("replied", (_b, reply) => {
|
|
17
|
+
resolve(reply);
|
|
18
|
+
});
|
|
19
|
+
bridge.connect("rejected", (_b, message) => {
|
|
20
|
+
reject(new Error(message));
|
|
21
|
+
});
|
|
22
|
+
emit(bridge.promise);
|
|
23
|
+
});
|
|
13
24
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { withGstPromise };
|