@gjsify/webrtc 0.3.15 → 0.3.17

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.
Files changed (37) hide show
  1. package/lib/esm/get-user-media.js +1 -108
  2. package/lib/esm/gst-enum-maps.js +1 -84
  3. package/lib/esm/gst-init.js +6 -28
  4. package/lib/esm/gst-stats-parser.js +1 -106
  5. package/lib/esm/gst-utils.js +1 -27
  6. package/lib/esm/index.js +1 -23
  7. package/lib/esm/media-device-info.js +1 -24
  8. package/lib/esm/media-devices.js +1 -158
  9. package/lib/esm/media-stream-track.js +1 -139
  10. package/lib/esm/media-stream.js +1 -79
  11. package/lib/esm/register/data-channel.js +1 -12
  12. package/lib/esm/register/error.js +1 -12
  13. package/lib/esm/register/media-devices.js +1 -11
  14. package/lib/esm/register/media.js +1 -16
  15. package/lib/esm/register/peer-connection.js +1 -20
  16. package/lib/esm/register.js +1 -5
  17. package/lib/esm/rtc-certificate.js +1 -66
  18. package/lib/esm/rtc-data-channel.js +1 -250
  19. package/lib/esm/rtc-dtls-transport.js +1 -42
  20. package/lib/esm/rtc-dtmf-sender.js +1 -101
  21. package/lib/esm/rtc-error.js +1 -26
  22. package/lib/esm/rtc-events.js +1 -35
  23. package/lib/esm/rtc-ice-candidate.js +1 -74
  24. package/lib/esm/rtc-ice-transport.js +1 -97
  25. package/lib/esm/rtc-peer-connection.js +1 -812
  26. package/lib/esm/rtc-rtp-receiver.js +1 -93
  27. package/lib/esm/rtc-rtp-sender.js +1 -290
  28. package/lib/esm/rtc-rtp-transceiver.js +1 -96
  29. package/lib/esm/rtc-sctp-transport.js +1 -40
  30. package/lib/esm/rtc-session-description.js +1 -53
  31. package/lib/esm/rtc-stats-report.js +1 -40
  32. package/lib/esm/rtc-track-event.js +1 -31
  33. package/lib/esm/rtp-capabilities.js +1 -87
  34. package/lib/esm/tee-multiplexer.js +1 -60
  35. package/lib/esm/wpt-helpers.js +1 -138
  36. package/package.json +14 -14
  37. package/tsconfig.tsbuildinfo +1 -1
@@ -1,108 +1 @@
1
- import { Gst, ensureGstInit } from "./gst-init.js";
2
- import { MediaStreamTrack } from "./media-stream-track.js";
3
- import { MediaStream } from "./media-stream.js";
4
-
5
- //#region src/get-user-media.ts
6
- async function getUserMedia(constraints) {
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);
55
- }
56
- /** Build a GStreamer caps string for audio constraints. */
57
- function _buildAudioCaps(c) {
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(",")}`;
63
- }
64
- /** Build a GStreamer caps string for video constraints. */
65
- function _buildVideoCaps(c) {
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(",")}`;
72
- }
73
- function _createAudioSource() {
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;
91
- }
92
- function _createVideoSource() {
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;
105
- }
106
-
107
- //#endregion
108
- export { getUserMedia };
1
+ import{Gst as e,ensureGstInit as t}from"./gst-init.js";import{MediaStreamTrack as n}from"./media-stream-track.js";import{MediaStream as r}from"./media-stream.js";async function i(i){if(t(),!i.audio&&!i.video)throw TypeError(`Failed to execute 'getUserMedia': At least one of audio or video must be requested`);let l=[];if(i.audio){let t=typeof i.audio==`object`?i.audio:{},r=s(),o=new e.Pipeline;o.add(r);let c=a(t);if(c){let t=e.ElementFactory.make(`capsfilter`,null);t.caps=e.Caps.from_string(c),o.add(t),r.link(t)}l.push(new n({kind:`audio`,label:r.name??`audio`,_gst:{source:r,pipeline:o}}))}if(i.video){let t=typeof i.video==`object`?i.video:{},r=c(),a=new e.Pipeline;a.add(r);let s=o(t);if(s){let t=e.ElementFactory.make(`capsfilter`,null);t.caps=e.Caps.from_string(s),a.add(t),r.link(t)}l.push(new n({kind:`video`,label:r.name??`video`,_gst:{source:r,pipeline:a}}))}return new r(l)}function a(e){let t=[];return e.sampleRate!=null&&t.push(`rate=${Math.trunc(e.sampleRate)}`),e.channelCount!=null&&t.push(`channels=${Math.trunc(e.channelCount)}`),t.length===0?null:`audio/x-raw,${t.join(`,`)}`}function o(e){let t=[];return e.width!=null&&t.push(`width=${Math.trunc(e.width)}`),e.height!=null&&t.push(`height=${Math.trunc(e.height)}`),e.frameRate!=null&&t.push(`framerate=${Math.trunc(e.frameRate)}/1`),t.length===0?null:`video/x-raw,${t.join(`,`)}`}function s(){for(let t of[`pipewiresrc`,`pulsesrc`,`autoaudiosrc`]){let n=e.ElementFactory.make(t,null);if(n){try{n.is_live=!0}catch{}return n}}let t=e.ElementFactory.make(`audiotestsrc`,null);return t.is_live=!0,t.wave=0,t}function c(){for(let t of[`pipewiresrc`,`v4l2src`,`autovideosrc`]){let n=e.ElementFactory.make(t,null);if(n)return n}let t=e.ElementFactory.make(`videotestsrc`,null);return t.is_live=!0,t.pattern=0,t}export{i as getUserMedia};
@@ -1,84 +1 @@
1
- import GstWebRTC from "gi://GstWebRTC?version=1.0";
2
-
3
- //#region src/gst-enum-maps.ts
4
- const SIGNALING_STATE_MAP = {
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"
11
- };
12
- function gstToSignalingState(v) {
13
- return SIGNALING_STATE_MAP[v] ?? "stable";
14
- }
15
- const CONNECTION_STATE_MAP = {
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"
22
- };
23
- function gstToConnectionState(v) {
24
- return CONNECTION_STATE_MAP[v] ?? "new";
25
- }
26
- const ICE_CONNECTION_STATE_MAP = {
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"
34
- };
35
- function gstToIceConnectionState(v) {
36
- return ICE_CONNECTION_STATE_MAP[v] ?? "new";
37
- }
38
- const ICE_GATHERING_STATE_MAP = {
39
- [GstWebRTC.WebRTCICEGatheringState.NEW]: "new",
40
- [GstWebRTC.WebRTCICEGatheringState.GATHERING]: "gathering",
41
- [GstWebRTC.WebRTCICEGatheringState.COMPLETE]: "complete"
42
- };
43
- function gstToIceGatheringState(v) {
44
- return ICE_GATHERING_STATE_MAP[v] ?? "new";
45
- }
46
- const DIRECTION_GST_TO_W3C = {
47
- [GstWebRTC.WebRTCRTPTransceiverDirection.SENDRECV]: "sendrecv",
48
- [GstWebRTC.WebRTCRTPTransceiverDirection.SENDONLY]: "sendonly",
49
- [GstWebRTC.WebRTCRTPTransceiverDirection.RECVONLY]: "recvonly"
50
- };
51
- const DIRECTION_W3C_TO_GST = {
52
- sendrecv: GstWebRTC.WebRTCRTPTransceiverDirection.SENDRECV,
53
- sendonly: GstWebRTC.WebRTCRTPTransceiverDirection.SENDONLY,
54
- recvonly: GstWebRTC.WebRTCRTPTransceiverDirection.RECVONLY,
55
- inactive: GstWebRTC.WebRTCRTPTransceiverDirection.NONE
56
- };
57
- function gstDirectionToW3C(v) {
58
- return DIRECTION_GST_TO_W3C[v] ?? "inactive";
59
- }
60
- function w3cDirectionToGst(d) {
61
- return DIRECTION_W3C_TO_GST[d] ?? GstWebRTC.WebRTCRTPTransceiverDirection.NONE;
62
- }
63
- const STATS_TYPE_MAP = {
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"
78
- };
79
- function gstToStatsType(v) {
80
- return STATS_TYPE_MAP[v];
81
- }
82
-
83
- //#endregion
84
- export { gstDirectionToW3C, gstToConnectionState, gstToIceConnectionState, gstToIceGatheringState, gstToSignalingState, gstToStatsType, w3cDirectionToGst };
1
+ import e from"gi://GstWebRTC?version=1.0";const t={[e.WebRTCSignalingState.STABLE]:`stable`,[e.WebRTCSignalingState.CLOSED]:`closed`,[e.WebRTCSignalingState.HAVE_LOCAL_OFFER]:`have-local-offer`,[e.WebRTCSignalingState.HAVE_REMOTE_OFFER]:`have-remote-offer`,[e.WebRTCSignalingState.HAVE_LOCAL_PRANSWER]:`have-local-pranswer`,[e.WebRTCSignalingState.HAVE_REMOTE_PRANSWER]:`have-remote-pranswer`};function n(e){return t[e]??`stable`}const r={[e.WebRTCPeerConnectionState.NEW]:`new`,[e.WebRTCPeerConnectionState.CONNECTING]:`connecting`,[e.WebRTCPeerConnectionState.CONNECTED]:`connected`,[e.WebRTCPeerConnectionState.DISCONNECTED]:`disconnected`,[e.WebRTCPeerConnectionState.FAILED]:`failed`,[e.WebRTCPeerConnectionState.CLOSED]:`closed`};function i(e){return r[e]??`new`}const a={[e.WebRTCICEConnectionState.NEW]:`new`,[e.WebRTCICEConnectionState.CHECKING]:`checking`,[e.WebRTCICEConnectionState.CONNECTED]:`connected`,[e.WebRTCICEConnectionState.COMPLETED]:`completed`,[e.WebRTCICEConnectionState.FAILED]:`failed`,[e.WebRTCICEConnectionState.DISCONNECTED]:`disconnected`,[e.WebRTCICEConnectionState.CLOSED]:`closed`};function o(e){return a[e]??`new`}const s={[e.WebRTCICEGatheringState.NEW]:`new`,[e.WebRTCICEGatheringState.GATHERING]:`gathering`,[e.WebRTCICEGatheringState.COMPLETE]:`complete`};function c(e){return s[e]??`new`}const l={[e.WebRTCRTPTransceiverDirection.SENDRECV]:`sendrecv`,[e.WebRTCRTPTransceiverDirection.SENDONLY]:`sendonly`,[e.WebRTCRTPTransceiverDirection.RECVONLY]:`recvonly`},u={sendrecv:e.WebRTCRTPTransceiverDirection.SENDRECV,sendonly:e.WebRTCRTPTransceiverDirection.SENDONLY,recvonly:e.WebRTCRTPTransceiverDirection.RECVONLY,inactive:e.WebRTCRTPTransceiverDirection.NONE};function d(e){return l[e]??`inactive`}function f(t){return u[t]??e.WebRTCRTPTransceiverDirection.NONE}const p={[e.WebRTCStatsType.CODEC]:`codec`,[e.WebRTCStatsType.INBOUND_RTP]:`inbound-rtp`,[e.WebRTCStatsType.OUTBOUND_RTP]:`outbound-rtp`,[e.WebRTCStatsType.REMOTE_INBOUND_RTP]:`remote-inbound-rtp`,[e.WebRTCStatsType.REMOTE_OUTBOUND_RTP]:`remote-outbound-rtp`,[e.WebRTCStatsType.CSRC]:`csrc`,[e.WebRTCStatsType.PEER_CONNECTION]:`peer-connection`,[e.WebRTCStatsType.DATA_CHANNEL]:`data-channel`,[e.WebRTCStatsType.STREAM]:`stream`,[e.WebRTCStatsType.TRANSPORT]:`transport`,[e.WebRTCStatsType.CANDIDATE_PAIR]:`candidate-pair`,[e.WebRTCStatsType.LOCAL_CANDIDATE]:`local-candidate`,[e.WebRTCStatsType.REMOTE_CANDIDATE]:`remote-candidate`,[e.WebRTCStatsType.CERTIFICATE]:`certificate`};function m(e){return p[e]}export{d as gstDirectionToW3C,i as gstToConnectionState,o as gstToIceConnectionState,c as gstToIceGatheringState,n as gstToSignalingState,m as gstToStatsType,f as w3cDirectionToGst};
@@ -1,28 +1,6 @@
1
- import Gst from "gi://Gst?version=1.0";
2
- import { DOMException } from "@gjsify/dom-exception";
3
-
4
- //#region src/gst-init.ts
5
- let initialized = false;
6
- function ensureGstInit() {
7
- if (initialized) return;
8
- Gst.init(null);
9
- initialized = true;
10
- }
11
- /** Throws if the `webrtcbin` element is not registered (gst-plugins-bad missing). */
12
- function ensureWebrtcbinAvailable() {
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
- }
22
- }
23
- function throwNotSupported(message) {
24
- throw new DOMException(message, "NotSupportedError");
25
- }
26
-
27
- //#endregion
28
- export { Gst, ensureGstInit, ensureWebrtcbinAvailable };
1
+ import e from"gi://Gst?version=1.0";import{DOMException as t}from"@gjsify/dom-exception";let n=!1;function r(){n||=(e.init(null),!0)}function i(){r(),e.ElementFactory.find(`webrtcbin`)||a(`GStreamer element "webrtcbin" not available. Install gst-plugins-bad:
2
+ Fedora: dnf install gstreamer1-plugins-bad-free gstreamer1-plugins-bad-free-extras
3
+ Ubuntu/Debian: apt install gstreamer1.0-plugins-bad`),e.ElementFactory.find(`nicesrc`)||a(`GStreamer "nice" plugin (libnice-gstreamer) not available — required by webrtcbin.
4
+ Fedora: dnf install libnice-gstreamer1
5
+ Ubuntu/Debian: apt install gstreamer1.0-nice
6
+ Verify with: gst-inspect-1.0 nicesrc`)}function a(e){throw new t(e,`NotSupportedError`)}export{e as Gst,r as ensureGstInit,i as ensureWebrtcbinAvailable};
@@ -1,106 +1 @@
1
- import { gstToStatsType } from "./gst-enum-maps.js";
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
- */
9
- function snakeToCamel(name) {
10
- return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
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
- */
17
- function extractFields(structure) {
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;
28
- }
29
- /**
30
- * Parse a single stats entry (nested GstStructure) into a W3C RTCStats object.
31
- */
32
- function parseStatsEntry(structure) {
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;
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
- */
60
- function parseGstStats(reply) {
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);
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
- */
83
- function filterStatsByTrackId(report, trackId) {
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);
103
- }
104
-
105
- //#endregion
106
- export { filterStatsByTrackId, parseGstStats };
1
+ import{gstToStatsType as e}from"./gst-enum-maps.js";import{RTCStatsReport as t}from"./rtc-stats-report.js";function n(e){return e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function r(e){let t={},n=e.n_fields();for(let r=0;r<n;r++){let n=e.nth_field_name(r);try{t[n]=e.get_value(n)}catch{}}return t}function i(t){let i=r(t),a=i.type;if(a==null)return null;let o=e(Number(a));if(!o)return null;let s=String(i.id??t.get_name()??``),c=i.timestamp,l={type:o,id:s,timestamp:c==null?performance.now():Number(c)/1e6};for(let[e,t]of Object.entries(i)){if(e===`type`||e===`id`||e===`timestamp`)continue;let r=n(e);l[r]=t}return l}function a(e){if(!e)return new t;let n=[],r=e.n_fields();for(let t=0;t<r;t++){let r=e.nth_field_name(t);try{let t=e.get_value(r);if(t&&typeof t.n_fields==`function`){let e=i(t);e&&n.push([e.id,e])}}catch{}}return new t(n)}function o(e,n){let r=[],i=new Set;for(let[t,a]of e)if(a.trackIdentifier===n){r.push([t,a]),i.add(t);for(let t of Object.values(a))typeof t==`string`&&e.has(t)&&i.add(t)}for(let[t,n]of e)i.has(t)&&!r.some(([e])=>e===t)&&r.push([t,n]);return new t(r)}export{o as filterStatsByTrackId,a as parseGstStats};
@@ -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
- */
13
- function withGstPromise(emit) {
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
- });
24
- }
25
-
26
- //#endregion
27
- export { withGstPromise };
1
+ import{PromiseBridge as e}from"@gjsify/webrtc-native";function t(t){return new Promise((n,r)=>{let i=new e;i.connect(`replied`,(e,t)=>{n(t)}),i.connect(`rejected`,(e,t)=>{r(Error(t))}),t(i.promise)})}export{t as withGstPromise};
package/lib/esm/index.js CHANGED
@@ -1,23 +1 @@
1
- import { MediaStreamTrack } from "./media-stream-track.js";
2
- import { MediaStream, MediaStreamTrackEvent } from "./media-stream.js";
3
- import { getUserMedia } from "./get-user-media.js";
4
- import { RTCStatsReport } from "./rtc-stats-report.js";
5
- import { RTCSessionDescription } from "./rtc-session-description.js";
6
- import { RTCIceCandidate } from "./rtc-ice-candidate.js";
7
- import { RTCError } from "./rtc-error.js";
8
- import { RTCDataChannelEvent, RTCErrorEvent, RTCPeerConnectionIceEvent } from "./rtc-events.js";
9
- import { RTCDataChannel } from "./rtc-data-channel.js";
10
- import { RTCDTMFSender, RTCDTMFToneChangeEvent } from "./rtc-dtmf-sender.js";
11
- import { RTCRtpSender } from "./rtc-rtp-sender.js";
12
- import { RTCRtpReceiver } from "./rtc-rtp-receiver.js";
13
- import { RTCRtpTransceiver } from "./rtc-rtp-transceiver.js";
14
- import { RTCTrackEvent } from "./rtc-track-event.js";
15
- import { RTCIceTransport } from "./rtc-ice-transport.js";
16
- import { RTCDtlsTransport } from "./rtc-dtls-transport.js";
17
- import { RTCSctpTransport } from "./rtc-sctp-transport.js";
18
- import { RTCCertificate } from "./rtc-certificate.js";
19
- import { RTCPeerConnection } from "./rtc-peer-connection.js";
20
- import { MediaDeviceInfo } from "./media-device-info.js";
21
- import { MediaDevices } from "./media-devices.js";
22
-
23
- export { MediaDeviceInfo, MediaDevices, MediaStream, MediaStreamTrack, MediaStreamTrackEvent, RTCCertificate, RTCDTMFSender, RTCDTMFToneChangeEvent, RTCDataChannel, RTCDataChannelEvent, RTCDtlsTransport, RTCError, RTCErrorEvent, RTCIceCandidate, RTCIceTransport, RTCPeerConnection, RTCPeerConnectionIceEvent, RTCRtpReceiver, RTCRtpSender, RTCRtpTransceiver, RTCSctpTransport, RTCSessionDescription, RTCStatsReport, RTCTrackEvent, getUserMedia };
1
+ import{MediaStreamTrack as e}from"./media-stream-track.js";import{MediaStream as t,MediaStreamTrackEvent as n}from"./media-stream.js";import{getUserMedia as r}from"./get-user-media.js";import{RTCStatsReport as i}from"./rtc-stats-report.js";import{RTCSessionDescription as a}from"./rtc-session-description.js";import{RTCIceCandidate as o}from"./rtc-ice-candidate.js";import{RTCError as s}from"./rtc-error.js";import{RTCDataChannelEvent as c,RTCErrorEvent as l,RTCPeerConnectionIceEvent as u}from"./rtc-events.js";import{RTCDataChannel as d}from"./rtc-data-channel.js";import{RTCDTMFSender as f,RTCDTMFToneChangeEvent as p}from"./rtc-dtmf-sender.js";import{RTCRtpSender as m}from"./rtc-rtp-sender.js";import{RTCRtpReceiver as h}from"./rtc-rtp-receiver.js";import{RTCRtpTransceiver as g}from"./rtc-rtp-transceiver.js";import{RTCTrackEvent as _}from"./rtc-track-event.js";import{RTCIceTransport as v}from"./rtc-ice-transport.js";import{RTCDtlsTransport as y}from"./rtc-dtls-transport.js";import{RTCSctpTransport as b}from"./rtc-sctp-transport.js";import{RTCCertificate as x}from"./rtc-certificate.js";import{RTCPeerConnection as S}from"./rtc-peer-connection.js";import{MediaDeviceInfo as C}from"./media-device-info.js";import{MediaDevices as w}from"./media-devices.js";export{C as MediaDeviceInfo,w as MediaDevices,t as MediaStream,e as MediaStreamTrack,n as MediaStreamTrackEvent,x as RTCCertificate,f as RTCDTMFSender,p as RTCDTMFToneChangeEvent,d as RTCDataChannel,c as RTCDataChannelEvent,y as RTCDtlsTransport,s as RTCError,l as RTCErrorEvent,o as RTCIceCandidate,v as RTCIceTransport,S as RTCPeerConnection,u as RTCPeerConnectionIceEvent,h as RTCRtpReceiver,m as RTCRtpSender,g as RTCRtpTransceiver,b as RTCSctpTransport,a as RTCSessionDescription,i as RTCStatsReport,_ as RTCTrackEvent,r as getUserMedia};
@@ -1,24 +1 @@
1
- //#region src/media-device-info.ts
2
- var MediaDeviceInfo = class {
3
- deviceId;
4
- kind;
5
- label;
6
- groupId;
7
- constructor(init) {
8
- this.deviceId = init.deviceId;
9
- this.kind = init.kind;
10
- this.label = init.label;
11
- this.groupId = init.groupId ?? "";
12
- }
13
- toJSON() {
14
- return {
15
- deviceId: this.deviceId,
16
- kind: this.kind,
17
- label: this.label,
18
- groupId: this.groupId
19
- };
20
- }
21
- };
22
-
23
- //#endregion
24
- export { MediaDeviceInfo };
1
+ var e=class{deviceId;kind;label;groupId;constructor(e){this.deviceId=e.deviceId,this.kind=e.kind,this.label=e.label,this.groupId=e.groupId??``}toJSON(){return{deviceId:this.deviceId,kind:this.kind,label:this.label,groupId:this.groupId}}};export{e as MediaDeviceInfo};
@@ -1,158 +1 @@
1
- import { Gst, ensureGstInit } from "./gst-init.js";
2
- import { getUserMedia } from "./get-user-media.js";
3
- import { MediaDeviceInfo } from "./media-device-info.js";
4
- import "@gjsify/dom-events/register/event-target";
5
-
6
- //#region src/media-devices.ts
7
- /** Map GStreamer device class strings to W3C MediaDeviceKind. */
8
- const DEVICE_CLASS_MAP = {
9
- "Audio/Source": "audioinput",
10
- "Video/Source": "videoinput",
11
- "Audio/Sink": "audiooutput"
12
- };
13
- /** Whether getUserMedia has been successfully called (unlocks full device info). */
14
- let _permissionGranted = false;
15
- /**
16
- * Check if GStreamer device monitoring is safe to use.
17
- * On some GJS/GStreamer combinations (e.g. Fedora 44 / GJS 1.88 in Docker),
18
- * DeviceMonitor and DeviceProviderFactory can SIGSEGV in native code — a crash
19
- * that JS error handling cannot intercept. We skip device monitoring entirely
20
- * when DISPLAY is absent (headless/CI) since there are typically no audio/video
21
- * devices in containers anyway.
22
- */
23
- function isDeviceMonitorSafe() {
24
- try {
25
- const GLib = imports.gi.GLib;
26
- if (GLib.getenv("CI")) return false;
27
- if (!GLib.getenv("DISPLAY") && !GLib.getenv("WAYLAND_DISPLAY")) return false;
28
- return true;
29
- } catch {
30
- return false;
31
- }
32
- }
33
- var MediaDevices = class extends EventTarget {
34
- _ondevicechange = null;
35
- get ondevicechange() {
36
- return this._ondevicechange;
37
- }
38
- set ondevicechange(v) {
39
- this._ondevicechange = v;
40
- }
41
- async getUserMedia(constraints) {
42
- if (!constraints) {
43
- throw new TypeError("Failed to execute 'getUserMedia' on 'MediaDevices': At least one of audio or video must be requested");
44
- }
45
- const stream = await getUserMedia(constraints);
46
- _permissionGranted = true;
47
- return stream;
48
- }
49
- async enumerateDevices() {
50
- ensureGstInit();
51
- let monitor = null;
52
- const result = [];
53
- try {
54
- if (!isDeviceMonitorSafe()) {
55
- return result;
56
- }
57
- monitor = new Gst.DeviceMonitor();
58
- monitor.set_show_all_devices(true);
59
- const audioCaps = Gst.Caps.from_string("audio/x-raw");
60
- const videoCaps = Gst.Caps.from_string("video/x-raw");
61
- if (audioCaps) {
62
- monitor.add_filter("Audio/Source", audioCaps);
63
- monitor.add_filter("Audio/Sink", audioCaps);
64
- }
65
- if (videoCaps) {
66
- monitor.add_filter("Video/Source", videoCaps);
67
- }
68
- if (!monitor.start()) {
69
- return result;
70
- }
71
- let gstDevices;
72
- try {
73
- gstDevices = monitor.get_devices() ?? [];
74
- } catch {
75
- return result;
76
- }
77
- for (const device of gstDevices) {
78
- const deviceClass = device.get_device_class?.() ?? "";
79
- const kind = DEVICE_CLASS_MAP[deviceClass];
80
- if (!kind) continue;
81
- const displayName = device.get_display_name?.() ?? "";
82
- let deviceId = "";
83
- let groupId = "";
84
- try {
85
- const props = device.get_properties?.();
86
- if (props) {
87
- const n = props.n_fields();
88
- for (let i = 0; i < n; i++) {
89
- const name = props.nth_field_name(i);
90
- if (name === "persistent-id" || name === "node.name") {
91
- const val = props.get_value(name);
92
- if (val && !deviceId) deviceId = String(val);
93
- }
94
- if (name === "group-id") {
95
- const val = props.get_value(name);
96
- if (val) groupId = String(val);
97
- }
98
- }
99
- }
100
- } catch {}
101
- if (!deviceId) {
102
- deviceId = displayName || `${kind}-${result.length}`;
103
- }
104
- if (_permissionGranted) {
105
- result.push(new MediaDeviceInfo({
106
- deviceId,
107
- kind,
108
- label: displayName,
109
- groupId
110
- }));
111
- } else {
112
- if (!result.some((d) => d.kind === kind)) {
113
- result.push(new MediaDeviceInfo({
114
- deviceId: "",
115
- kind,
116
- label: "",
117
- groupId: ""
118
- }));
119
- }
120
- }
121
- }
122
- } catch {
123
- return result;
124
- } finally {
125
- try {
126
- monitor?.stop();
127
- } catch {}
128
- }
129
- const order = {
130
- audioinput: 0,
131
- videoinput: 1,
132
- audiooutput: 2
133
- };
134
- result.sort((a, b) => (order[a.kind] ?? 3) - (order[b.kind] ?? 3));
135
- return result;
136
- }
137
- getSupportedConstraints() {
138
- return {
139
- deviceId: true,
140
- width: true,
141
- height: true,
142
- frameRate: true,
143
- sampleRate: true,
144
- channelCount: true,
145
- aspectRatio: false,
146
- facingMode: false,
147
- resizeMode: false,
148
- echoCancellation: false,
149
- autoGainControl: false,
150
- noiseSuppression: false,
151
- latency: false,
152
- groupId: false
153
- };
154
- }
155
- };
156
-
157
- //#endregion
158
- export { MediaDevices };
1
+ import{Gst as e,ensureGstInit as t}from"./gst-init.js";import{getUserMedia as n}from"./get-user-media.js";import{MediaDeviceInfo as r}from"./media-device-info.js";import"@gjsify/dom-events/register/event-target";const i={"Audio/Source":`audioinput`,"Video/Source":`videoinput`,"Audio/Sink":`audiooutput`};let a=!1;function o(){try{let e=imports.gi.GLib;return!(e.getenv(`CI`)||!e.getenv(`DISPLAY`)&&!e.getenv(`WAYLAND_DISPLAY`))}catch{return!1}}var s=class extends EventTarget{_ondevicechange=null;get ondevicechange(){return this._ondevicechange}set ondevicechange(e){this._ondevicechange=e}async getUserMedia(e){if(!e)throw TypeError(`Failed to execute 'getUserMedia' on 'MediaDevices': At least one of audio or video must be requested`);let t=await n(e);return a=!0,t}async enumerateDevices(){t();let n=null,s=[];try{if(!o())return s;n=new e.DeviceMonitor,n.set_show_all_devices(!0);let t=e.Caps.from_string(`audio/x-raw`),c=e.Caps.from_string(`video/x-raw`);if(t&&(n.add_filter(`Audio/Source`,t),n.add_filter(`Audio/Sink`,t)),c&&n.add_filter(`Video/Source`,c),!n.start())return s;let l;try{l=n.get_devices()??[]}catch{return s}for(let e of l){let t=i[e.get_device_class?.()??``];if(!t)continue;let n=e.get_display_name?.()??``,o=``,c=``;try{let t=e.get_properties?.();if(t){let e=t.n_fields();for(let n=0;n<e;n++){let e=t.nth_field_name(n);if(e===`persistent-id`||e===`node.name`){let n=t.get_value(e);n&&!o&&(o=String(n))}if(e===`group-id`){let n=t.get_value(e);n&&(c=String(n))}}}}catch{}o||=n||`${t}-${s.length}`,a?s.push(new r({deviceId:o,kind:t,label:n,groupId:c})):s.some(e=>e.kind===t)||s.push(new r({deviceId:``,kind:t,label:``,groupId:``}))}}catch{return s}finally{try{n?.stop()}catch{}}let c={audioinput:0,videoinput:1,audiooutput:2};return s.sort((e,t)=>(c[e.kind]??3)-(c[t.kind]??3)),s}getSupportedConstraints(){return{deviceId:!0,width:!0,height:!0,frameRate:!0,sampleRate:!0,channelCount:!0,aspectRatio:!1,facingMode:!1,resizeMode:!1,echoCancellation:!1,autoGainControl:!1,noiseSuppression:!1,latency:!1,groupId:!1}}};export{s as MediaDevices};