@gjsify/webrtc 0.1.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 +93 -0
- package/lib/esm/gst-enum-maps.js +88 -0
- package/lib/esm/gst-init.js +34 -0
- package/lib/esm/gst-stats-parser.js +79 -0
- package/lib/esm/gst-utils.js +16 -0
- package/lib/esm/index.js +53 -0
- package/lib/esm/media-device-info.js +23 -0
- package/lib/esm/media-devices.js +147 -0
- package/lib/esm/media-stream-track.js +142 -0
- package/lib/esm/media-stream.js +78 -0
- package/lib/esm/register/data-channel.js +8 -0
- package/lib/esm/register/error.js +8 -0
- package/lib/esm/register/media-devices.js +7 -0
- package/lib/esm/register/media.js +12 -0
- package/lib/esm/register/peer-connection.js +16 -0
- package/lib/esm/register.js +5 -0
- package/lib/esm/rtc-certificate.js +70 -0
- package/lib/esm/rtc-data-channel.js +266 -0
- package/lib/esm/rtc-dtls-transport.js +41 -0
- package/lib/esm/rtc-dtmf-sender.js +109 -0
- package/lib/esm/rtc-error.js +24 -0
- package/lib/esm/rtc-events.js +35 -0
- package/lib/esm/rtc-ice-candidate.js +75 -0
- package/lib/esm/rtc-ice-transport.js +96 -0
- package/lib/esm/rtc-peer-connection.js +855 -0
- package/lib/esm/rtc-rtp-receiver.js +91 -0
- package/lib/esm/rtc-rtp-sender.js +298 -0
- package/lib/esm/rtc-rtp-transceiver.js +97 -0
- package/lib/esm/rtc-sctp-transport.js +40 -0
- package/lib/esm/rtc-session-description.js +57 -0
- package/lib/esm/rtc-stats-report.js +35 -0
- package/lib/esm/rtc-track-event.js +29 -0
- package/lib/esm/rtp-capabilities.js +41 -0
- package/lib/esm/tee-multiplexer.js +62 -0
- package/lib/esm/wpt-helpers.js +122 -0
- package/lib/types/get-user-media.d.ts +14 -0
- package/lib/types/gst-enum-maps.d.ts +10 -0
- package/lib/types/gst-init.d.ts +5 -0
- package/lib/types/gst-stats-parser.d.ts +16 -0
- package/lib/types/gst-utils.d.ts +11 -0
- package/lib/types/index.d.ts +41 -0
- package/lib/types/media-device-info.d.ts +14 -0
- package/lib/types/media-devices.d.ts +12 -0
- package/lib/types/media-stream-track.d.ts +59 -0
- package/lib/types/media-stream.d.ts +28 -0
- package/lib/types/register/data-channel.d.ts +1 -0
- package/lib/types/register/error.d.ts +1 -0
- package/lib/types/register/media-devices.d.ts +1 -0
- package/lib/types/register/media.d.ts +1 -0
- package/lib/types/register/peer-connection.d.ts +1 -0
- package/lib/types/register.d.ts +5 -0
- package/lib/types/register.spec.d.ts +3 -0
- package/lib/types/rtc-certificate.d.ts +23 -0
- package/lib/types/rtc-data-channel.d.ts +64 -0
- package/lib/types/rtc-dtls-transport.d.ts +20 -0
- package/lib/types/rtc-dtmf-sender.d.ts +31 -0
- package/lib/types/rtc-error.d.ts +19 -0
- package/lib/types/rtc-events.d.ts +27 -0
- package/lib/types/rtc-ice-candidate.d.ts +28 -0
- package/lib/types/rtc-ice-transport.d.ts +56 -0
- package/lib/types/rtc-peer-connection.d.ts +165 -0
- package/lib/types/rtc-rtp-receiver.d.ts +45 -0
- package/lib/types/rtc-rtp-sender.d.ts +98 -0
- package/lib/types/rtc-rtp-transceiver.d.ts +20 -0
- package/lib/types/rtc-sctp-transport.d.ts +20 -0
- package/lib/types/rtc-session-description.d.ts +18 -0
- package/lib/types/rtc-stats-report.d.ts +22 -0
- package/lib/types/rtc-track-event.d.ts +18 -0
- package/lib/types/rtp-capabilities.d.ts +3 -0
- package/lib/types/tee-multiplexer.d.ts +25 -0
- package/lib/types/webrtc.spec.d.ts +2 -0
- package/lib/types/wpt-helpers.d.ts +30 -0
- package/lib/types/wpt-media.spec.d.ts +2 -0
- package/lib/types/wpt.spec.d.ts +2 -0
- package/package.json +74 -0
- package/src/get-user-media.ts +131 -0
- package/src/gst-enum-maps.ts +125 -0
- package/src/gst-init.ts +52 -0
- package/src/gst-stats-parser.ts +137 -0
- package/src/gst-utils.ts +41 -0
- package/src/index.ts +104 -0
- package/src/media-device-info.ts +33 -0
- package/src/media-devices.ts +191 -0
- package/src/media-stream-track.ts +159 -0
- package/src/media-stream.ts +96 -0
- package/src/register/data-channel.ts +11 -0
- package/src/register/error.ts +11 -0
- package/src/register/media-devices.ts +10 -0
- package/src/register/media.ts +15 -0
- package/src/register/peer-connection.ts +20 -0
- package/src/register.spec.ts +55 -0
- package/src/register.ts +10 -0
- package/src/rtc-certificate.ts +110 -0
- package/src/rtc-data-channel.ts +284 -0
- package/src/rtc-dtls-transport.ts +48 -0
- package/src/rtc-dtmf-sender.ts +146 -0
- package/src/rtc-error.ts +49 -0
- package/src/rtc-events.ts +64 -0
- package/src/rtc-ice-candidate.ts +115 -0
- package/src/rtc-ice-transport.ts +104 -0
- package/src/rtc-peer-connection.ts +1017 -0
- package/src/rtc-rtp-receiver.ts +122 -0
- package/src/rtc-rtp-sender.ts +444 -0
- package/src/rtc-rtp-transceiver.ts +127 -0
- package/src/rtc-sctp-transport.ts +48 -0
- package/src/rtc-session-description.ts +64 -0
- package/src/rtc-stats-report.ts +39 -0
- package/src/rtc-track-event.ts +45 -0
- package/src/rtp-capabilities.ts +48 -0
- package/src/tee-multiplexer.ts +75 -0
- package/src/test.mts +11 -0
- package/src/webrtc.spec.ts +1186 -0
- package/src/wpt-helpers.ts +156 -0
- package/src/wpt-media.spec.ts +1154 -0
- package/src/wpt.spec.ts +1136 -0
- package/tsconfig.json +36 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ReceiverBridge
|
|
3
|
+
} from "@gjsify/webrtc-native";
|
|
4
|
+
import { MediaStreamTrack } from "./media-stream-track.js";
|
|
5
|
+
import { getRtpCapabilities } from "./rtp-capabilities.js";
|
|
6
|
+
const MAX_JITTER_BUFFER_TARGET = 4e3;
|
|
7
|
+
class RTCRtpReceiver {
|
|
8
|
+
_gstReceiver;
|
|
9
|
+
_track;
|
|
10
|
+
_jitterBufferTarget = null;
|
|
11
|
+
_pipeline = null;
|
|
12
|
+
_receiverBridge = null;
|
|
13
|
+
/** @internal — stats callback set by RTCPeerConnection */
|
|
14
|
+
_getStatsForTrack = null;
|
|
15
|
+
constructor(kind, gstReceiver, pipeline) {
|
|
16
|
+
this._gstReceiver = gstReceiver;
|
|
17
|
+
this._pipeline = pipeline ?? null;
|
|
18
|
+
this._track = new MediaStreamTrack({ kind, muted: true });
|
|
19
|
+
}
|
|
20
|
+
/** @internal — called from RTCPeerConnection._handlePadAdded */
|
|
21
|
+
_connectToPad(pad) {
|
|
22
|
+
if (!this._pipeline || this._receiverBridge) return;
|
|
23
|
+
this._receiverBridge = new ReceiverBridge({
|
|
24
|
+
pipeline: this._pipeline,
|
|
25
|
+
kind: this._track.kind
|
|
26
|
+
});
|
|
27
|
+
this._receiverBridge.connect_to_pad(pad);
|
|
28
|
+
this._receiverBridge.connect("media-flowing", () => {
|
|
29
|
+
this._track._setMuted(false);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** @internal — called from RTCPeerConnection.close() */
|
|
33
|
+
_dispose() {
|
|
34
|
+
try {
|
|
35
|
+
this._receiverBridge?.dispose_bridge();
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
this._receiverBridge = null;
|
|
39
|
+
}
|
|
40
|
+
/** @internal — set by RTCPeerConnection */
|
|
41
|
+
_transport = null;
|
|
42
|
+
get track() {
|
|
43
|
+
return this._track;
|
|
44
|
+
}
|
|
45
|
+
get transport() {
|
|
46
|
+
return this._transport;
|
|
47
|
+
}
|
|
48
|
+
get jitterBufferTarget() {
|
|
49
|
+
return this._jitterBufferTarget;
|
|
50
|
+
}
|
|
51
|
+
set jitterBufferTarget(v) {
|
|
52
|
+
if (v === null) {
|
|
53
|
+
this._jitterBufferTarget = null;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const n = Number(v);
|
|
57
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
58
|
+
throw new RangeError(`Failed to set jitterBufferTarget: ${v} is negative or not finite`);
|
|
59
|
+
}
|
|
60
|
+
if (n > MAX_JITTER_BUFFER_TARGET) {
|
|
61
|
+
throw new RangeError(`Failed to set jitterBufferTarget: ${v} exceeds maximum of ${MAX_JITTER_BUFFER_TARGET}`);
|
|
62
|
+
}
|
|
63
|
+
this._jitterBufferTarget = n;
|
|
64
|
+
}
|
|
65
|
+
getParameters() {
|
|
66
|
+
return {
|
|
67
|
+
codecs: [],
|
|
68
|
+
headerExtensions: [],
|
|
69
|
+
rtcp: {}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
getContributingSources() {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
getSynchronizationSources() {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
async getStats() {
|
|
79
|
+
if (this._getStatsForTrack && this._track) {
|
|
80
|
+
return this._getStatsForTrack(this._track);
|
|
81
|
+
}
|
|
82
|
+
const { RTCStatsReport: Report } = await import("./rtc-stats-report.js");
|
|
83
|
+
return new Report();
|
|
84
|
+
}
|
|
85
|
+
static getCapabilities(kind) {
|
|
86
|
+
return getRtpCapabilities(kind);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
RTCRtpReceiver
|
|
91
|
+
};
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { Gst } from "./gst-init.js";
|
|
2
|
+
import { getRtpCapabilities } from "./rtp-capabilities.js";
|
|
3
|
+
import { RTCDTMFSender } from "./rtc-dtmf-sender.js";
|
|
4
|
+
import { TeeMultiplexer } from "./tee-multiplexer.js";
|
|
5
|
+
const OPUS_PAYLOAD_TYPE = 111;
|
|
6
|
+
const VP8_PAYLOAD_TYPE = 96;
|
|
7
|
+
let _txCounter = 0;
|
|
8
|
+
class RTCRtpSender {
|
|
9
|
+
_gstSender;
|
|
10
|
+
_track = null;
|
|
11
|
+
_lastParams = null;
|
|
12
|
+
/** @internal GStreamer pipeline references (set by RTCPeerConnection) */
|
|
13
|
+
_pipeline = null;
|
|
14
|
+
_webrtcbin = null;
|
|
15
|
+
_mlineIndex = -1;
|
|
16
|
+
_elements = [];
|
|
17
|
+
_valve = null;
|
|
18
|
+
_linked = false;
|
|
19
|
+
/** @internal — tee src pad if this sender uses a shared source */
|
|
20
|
+
_teeSrcPad = null;
|
|
21
|
+
/** @internal — stats callback set by RTCPeerConnection */
|
|
22
|
+
_getStatsForTrack = null;
|
|
23
|
+
/** @internal — set by RTCPeerConnection */
|
|
24
|
+
_transport = null;
|
|
25
|
+
/** @internal — DTMF sender, created lazily for audio senders */
|
|
26
|
+
_dtmf = null;
|
|
27
|
+
/** @internal — the kind of media this sender handles */
|
|
28
|
+
_kind = null;
|
|
29
|
+
/** @internal — back-reference for DTMF stopped/direction checks */
|
|
30
|
+
_transceiver = null;
|
|
31
|
+
/** @internal — callback to notify RTCPeerConnection when pipeline changes (cross-pipeline fix) */
|
|
32
|
+
_onPipelineChanged = null;
|
|
33
|
+
constructor(gstSender, pipeline, webrtcbin) {
|
|
34
|
+
this._gstSender = gstSender;
|
|
35
|
+
this._pipeline = pipeline ?? null;
|
|
36
|
+
this._webrtcbin = webrtcbin ?? null;
|
|
37
|
+
}
|
|
38
|
+
get track() {
|
|
39
|
+
return this._track;
|
|
40
|
+
}
|
|
41
|
+
/** Returns the DTMF sender for audio senders, null for video. */
|
|
42
|
+
get dtmf() {
|
|
43
|
+
const kind = this._track?.kind ?? this._kind;
|
|
44
|
+
if (kind !== "audio") return null;
|
|
45
|
+
if (!this._dtmf) {
|
|
46
|
+
const dtmf = new RTCDTMFSender();
|
|
47
|
+
dtmf._isStopped = () => this._transceiver?.stopped ?? false;
|
|
48
|
+
dtmf._getCurrentDirection = () => this._transceiver?.currentDirection ?? null;
|
|
49
|
+
this._dtmf = dtmf;
|
|
50
|
+
}
|
|
51
|
+
return this._dtmf;
|
|
52
|
+
}
|
|
53
|
+
get transport() {
|
|
54
|
+
return this._transport;
|
|
55
|
+
}
|
|
56
|
+
/** @internal */
|
|
57
|
+
_setTrack(track) {
|
|
58
|
+
if (track === null && this._linked) {
|
|
59
|
+
this._teardownPipeline();
|
|
60
|
+
}
|
|
61
|
+
this._track = track;
|
|
62
|
+
}
|
|
63
|
+
/** @internal — called by RTCPeerConnection._createTransceiverWrapper */
|
|
64
|
+
_setMlineIndex(index) {
|
|
65
|
+
this._mlineIndex = index;
|
|
66
|
+
}
|
|
67
|
+
/** @internal — build the outgoing encoder chain and link to webrtcbin */
|
|
68
|
+
_wirePipeline(track) {
|
|
69
|
+
if (this._linked || !this._pipeline || !this._webrtcbin) return;
|
|
70
|
+
const source = track._gstSource;
|
|
71
|
+
if (!source) return;
|
|
72
|
+
const trackAny = track;
|
|
73
|
+
let sourceForChain;
|
|
74
|
+
if (trackAny._gstTee && trackAny._gstPipeline && trackAny._gstPipeline !== this._pipeline) {
|
|
75
|
+
const sourcePipeline = trackAny._gstPipeline;
|
|
76
|
+
const tee = trackAny._gstTee;
|
|
77
|
+
if (this._webrtcbin.get_parent() === this._pipeline) {
|
|
78
|
+
this._pipeline.set_state(Gst.State.NULL);
|
|
79
|
+
this._pipeline.remove(this._webrtcbin);
|
|
80
|
+
}
|
|
81
|
+
sourcePipeline.add(this._webrtcbin);
|
|
82
|
+
this._webrtcbin.sync_state_with_parent();
|
|
83
|
+
this._pipeline = sourcePipeline;
|
|
84
|
+
this._onPipelineChanged?.(sourcePipeline);
|
|
85
|
+
const teeSrcPad = tee.request_pad_simple ? tee.request_pad_simple("src_%u") : tee.get_request_pad("src_%u");
|
|
86
|
+
this._teeSrcPad = teeSrcPad;
|
|
87
|
+
sourceForChain = null;
|
|
88
|
+
} else if (trackAny._teeMultiplexer) {
|
|
89
|
+
const tee = trackAny._teeMultiplexer;
|
|
90
|
+
const teeSrcPad = tee.requestSrcPad();
|
|
91
|
+
this._teeSrcPad = teeSrcPad;
|
|
92
|
+
sourceForChain = null;
|
|
93
|
+
} else if (trackAny._gstPipeline && trackAny._gstPipeline !== this._pipeline) {
|
|
94
|
+
const oldPipeline = trackAny._gstPipeline;
|
|
95
|
+
const sourceSrcPad = source.get_static_pad("src");
|
|
96
|
+
const oldPeer = sourceSrcPad?.get_peer?.();
|
|
97
|
+
if (oldPeer) sourceSrcPad.unlink(oldPeer);
|
|
98
|
+
source.set_state(Gst.State.NULL);
|
|
99
|
+
oldPipeline.remove(source);
|
|
100
|
+
this._pipeline.add(source);
|
|
101
|
+
trackAny._gstPipeline = this._pipeline;
|
|
102
|
+
const tee = new TeeMultiplexer(this._pipeline, source);
|
|
103
|
+
trackAny._teeMultiplexer = tee;
|
|
104
|
+
if (oldPeer) {
|
|
105
|
+
const firstBranch = tee.requestSrcPad();
|
|
106
|
+
if (firstBranch) firstBranch.link(oldPeer);
|
|
107
|
+
}
|
|
108
|
+
const teeSrcPad = tee.requestSrcPad();
|
|
109
|
+
this._teeSrcPad = teeSrcPad;
|
|
110
|
+
sourceForChain = null;
|
|
111
|
+
} else {
|
|
112
|
+
const oldPipeline = trackAny._gstPipeline;
|
|
113
|
+
if (oldPipeline && oldPipeline !== this._pipeline) {
|
|
114
|
+
source.set_state(Gst.State.NULL);
|
|
115
|
+
oldPipeline.remove(source);
|
|
116
|
+
trackAny._gstPipeline = this._pipeline;
|
|
117
|
+
}
|
|
118
|
+
if (source.get_parent() !== this._pipeline) {
|
|
119
|
+
this._pipeline.add(source);
|
|
120
|
+
}
|
|
121
|
+
sourceForChain = source;
|
|
122
|
+
}
|
|
123
|
+
const valve = Gst.ElementFactory.make("valve", null);
|
|
124
|
+
valve.drop = !track.enabled;
|
|
125
|
+
this._valve = valve;
|
|
126
|
+
this._pipeline.add(valve);
|
|
127
|
+
const elements = [valve];
|
|
128
|
+
let lastElement;
|
|
129
|
+
if (track.kind === "audio") {
|
|
130
|
+
const convert = Gst.ElementFactory.make("audioconvert", null);
|
|
131
|
+
const resample = Gst.ElementFactory.make("audioresample", null);
|
|
132
|
+
const encoder = Gst.ElementFactory.make("opusenc", null);
|
|
133
|
+
const payloader = Gst.ElementFactory.make("rtpopuspay", null);
|
|
134
|
+
payloader.pt = OPUS_PAYLOAD_TYPE;
|
|
135
|
+
const capsfilter = Gst.ElementFactory.make("capsfilter", null);
|
|
136
|
+
capsfilter.caps = Gst.Caps.from_string(
|
|
137
|
+
`application/x-rtp,media=audio,encoding-name=OPUS,clock-rate=48000,payload=${OPUS_PAYLOAD_TYPE}`
|
|
138
|
+
);
|
|
139
|
+
elements.push(convert, resample, encoder, payloader, capsfilter);
|
|
140
|
+
for (const el of elements) this._pipeline.add(el);
|
|
141
|
+
if (this._teeSrcPad) {
|
|
142
|
+
const valveSinkPad = valve.get_static_pad("sink");
|
|
143
|
+
this._teeSrcPad.link(valveSinkPad);
|
|
144
|
+
} else if (sourceForChain) {
|
|
145
|
+
sourceForChain.link(valve);
|
|
146
|
+
}
|
|
147
|
+
valve.link(convert);
|
|
148
|
+
convert.link(resample);
|
|
149
|
+
resample.link(encoder);
|
|
150
|
+
encoder.link(payloader);
|
|
151
|
+
payloader.link(capsfilter);
|
|
152
|
+
lastElement = capsfilter;
|
|
153
|
+
} else {
|
|
154
|
+
const convert = Gst.ElementFactory.make("videoconvert", null);
|
|
155
|
+
const scale = Gst.ElementFactory.make("videoscale", null);
|
|
156
|
+
const encoder = Gst.ElementFactory.make("vp8enc", null);
|
|
157
|
+
encoder.deadline = 1;
|
|
158
|
+
encoder.keyframe_max_dist = 60;
|
|
159
|
+
const payloader = Gst.ElementFactory.make("rtpvp8pay", null);
|
|
160
|
+
payloader.pt = VP8_PAYLOAD_TYPE;
|
|
161
|
+
const capsfilter = Gst.ElementFactory.make("capsfilter", null);
|
|
162
|
+
capsfilter.caps = Gst.Caps.from_string(
|
|
163
|
+
`application/x-rtp,media=video,encoding-name=VP8,clock-rate=90000,payload=${VP8_PAYLOAD_TYPE}`
|
|
164
|
+
);
|
|
165
|
+
elements.push(convert, scale, encoder, payloader, capsfilter);
|
|
166
|
+
for (const el of elements) this._pipeline.add(el);
|
|
167
|
+
if (this._teeSrcPad) {
|
|
168
|
+
const valveSinkPad = valve.get_static_pad("sink");
|
|
169
|
+
this._teeSrcPad.link(valveSinkPad);
|
|
170
|
+
} else if (sourceForChain) {
|
|
171
|
+
sourceForChain.link(valve);
|
|
172
|
+
}
|
|
173
|
+
valve.link(convert);
|
|
174
|
+
convert.link(scale);
|
|
175
|
+
scale.link(encoder);
|
|
176
|
+
encoder.link(payloader);
|
|
177
|
+
payloader.link(capsfilter);
|
|
178
|
+
lastElement = capsfilter;
|
|
179
|
+
}
|
|
180
|
+
const padName = this._mlineIndex >= 0 ? `sink_${this._mlineIndex}` : "sink_%u";
|
|
181
|
+
const sinkPad = this._webrtcbin.request_pad_simple ? this._webrtcbin.request_pad_simple(padName) : this._webrtcbin.get_request_pad(padName);
|
|
182
|
+
if (sinkPad) {
|
|
183
|
+
const srcPad = lastElement.get_static_pad("src");
|
|
184
|
+
srcPad.link(sinkPad);
|
|
185
|
+
}
|
|
186
|
+
const ownedElements = this._teeSrcPad ? elements : [source, ...elements];
|
|
187
|
+
for (const el of ownedElements) {
|
|
188
|
+
el.sync_state_with_parent();
|
|
189
|
+
}
|
|
190
|
+
this._elements = ownedElements;
|
|
191
|
+
this._linked = true;
|
|
192
|
+
track._setEnableCallback((enabled) => {
|
|
193
|
+
if (this._valve) this._valve.drop = !enabled;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/** @internal — tear down the encoder chain on close/removeTrack */
|
|
197
|
+
_teardownPipeline() {
|
|
198
|
+
if (!this._linked) return;
|
|
199
|
+
if (this._track) {
|
|
200
|
+
this._track._setEnableCallback(null);
|
|
201
|
+
}
|
|
202
|
+
if (this._teeSrcPad && this._track?._teeMultiplexer) {
|
|
203
|
+
try {
|
|
204
|
+
this._track._teeMultiplexer.releaseSrcPad(this._teeSrcPad);
|
|
205
|
+
} catch {
|
|
206
|
+
}
|
|
207
|
+
this._teeSrcPad = null;
|
|
208
|
+
}
|
|
209
|
+
for (const el of [...this._elements].reverse()) {
|
|
210
|
+
try {
|
|
211
|
+
el.set_state(Gst.State.NULL);
|
|
212
|
+
this._pipeline?.remove(el);
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
this._elements = [];
|
|
217
|
+
this._valve = null;
|
|
218
|
+
this._linked = false;
|
|
219
|
+
}
|
|
220
|
+
getParameters() {
|
|
221
|
+
if (!this._lastParams) {
|
|
222
|
+
this._lastParams = {
|
|
223
|
+
transactionId: String(++_txCounter),
|
|
224
|
+
encodings: [],
|
|
225
|
+
codecs: [],
|
|
226
|
+
headerExtensions: [],
|
|
227
|
+
rtcp: {}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return { ...this._lastParams, encodings: [...this._lastParams.encodings] };
|
|
231
|
+
}
|
|
232
|
+
async setParameters(params) {
|
|
233
|
+
if (!this._lastParams) {
|
|
234
|
+
throw new DOMException(
|
|
235
|
+
"getParameters must be called before setParameters",
|
|
236
|
+
"InvalidStateError"
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
if (params.transactionId !== this._lastParams.transactionId) {
|
|
240
|
+
throw new DOMException(
|
|
241
|
+
"transactionId mismatch",
|
|
242
|
+
"InvalidModificationError"
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
this._lastParams = null;
|
|
246
|
+
}
|
|
247
|
+
async replaceTrack(track) {
|
|
248
|
+
if (track === null) {
|
|
249
|
+
this._teardownPipeline();
|
|
250
|
+
this._track = null;
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (this._track !== null && track.kind !== this._track.kind) {
|
|
254
|
+
throw new TypeError("Cannot replace track with different kind");
|
|
255
|
+
}
|
|
256
|
+
if (this._linked && track._gstSource) {
|
|
257
|
+
const oldSource = this._elements[0];
|
|
258
|
+
const newSource = track._gstSource;
|
|
259
|
+
const oldPipeline = track._gstPipeline;
|
|
260
|
+
if (oldPipeline && oldPipeline !== this._pipeline) {
|
|
261
|
+
newSource.set_state(Gst.State.NULL);
|
|
262
|
+
oldPipeline.remove(newSource);
|
|
263
|
+
track._gstPipeline = this._pipeline;
|
|
264
|
+
}
|
|
265
|
+
oldSource.set_state(Gst.State.NULL);
|
|
266
|
+
oldSource.unlink(this._valve);
|
|
267
|
+
this._pipeline.remove(oldSource);
|
|
268
|
+
this._pipeline.add(newSource);
|
|
269
|
+
newSource.link(this._valve);
|
|
270
|
+
newSource.sync_state_with_parent();
|
|
271
|
+
this._elements[0] = newSource;
|
|
272
|
+
} else if (track._gstSource) {
|
|
273
|
+
this._wirePipeline(track);
|
|
274
|
+
}
|
|
275
|
+
if (this._track) this._track._setEnableCallback(null);
|
|
276
|
+
this._track = track;
|
|
277
|
+
if (this._linked) {
|
|
278
|
+
track._setEnableCallback((enabled) => {
|
|
279
|
+
if (this._valve) this._valve.drop = !enabled;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async getStats() {
|
|
284
|
+
if (this._getStatsForTrack && this._track) {
|
|
285
|
+
return this._getStatsForTrack(this._track);
|
|
286
|
+
}
|
|
287
|
+
const { RTCStatsReport: Report } = await import("./rtc-stats-report.js");
|
|
288
|
+
return new Report();
|
|
289
|
+
}
|
|
290
|
+
setStreams(..._streams) {
|
|
291
|
+
}
|
|
292
|
+
static getCapabilities(kind) {
|
|
293
|
+
return getRtpCapabilities(kind);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
export {
|
|
297
|
+
RTCRtpSender
|
|
298
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { gstDirectionToW3C, w3cDirectionToGst } from "./gst-enum-maps.js";
|
|
2
|
+
import { RTCRtpSender } from "./rtc-rtp-sender.js";
|
|
3
|
+
import { RTCRtpReceiver } from "./rtc-rtp-receiver.js";
|
|
4
|
+
class RTCRtpTransceiver {
|
|
5
|
+
_gstTrans;
|
|
6
|
+
sender;
|
|
7
|
+
receiver;
|
|
8
|
+
_stopped = false;
|
|
9
|
+
_codecPreferences = [];
|
|
10
|
+
constructor(gstTrans, sender, receiver) {
|
|
11
|
+
this._gstTrans = gstTrans;
|
|
12
|
+
this.sender = sender;
|
|
13
|
+
this.receiver = receiver;
|
|
14
|
+
}
|
|
15
|
+
get mid() {
|
|
16
|
+
if (this._stopped) return null;
|
|
17
|
+
const m = this._gstTrans.mid;
|
|
18
|
+
return m === "" || m == null ? null : String(m);
|
|
19
|
+
}
|
|
20
|
+
get direction() {
|
|
21
|
+
if (this._stopped) return "stopped";
|
|
22
|
+
return gstDirectionToW3C(this._gstTrans.direction);
|
|
23
|
+
}
|
|
24
|
+
set direction(d) {
|
|
25
|
+
if (this._stopped) {
|
|
26
|
+
throw new DOMException(
|
|
27
|
+
"Cannot set direction on a stopped transceiver",
|
|
28
|
+
"InvalidStateError"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (d === "stopped") {
|
|
32
|
+
throw new TypeError("The provided value 'stopped' is not a valid enum value of type RTCRtpTransceiverDirection.");
|
|
33
|
+
}
|
|
34
|
+
const valid = ["sendrecv", "sendonly", "recvonly", "inactive"];
|
|
35
|
+
if (!valid.includes(d)) {
|
|
36
|
+
throw new TypeError(`The provided value '${d}' is not a valid enum value of type RTCRtpTransceiverDirection.`);
|
|
37
|
+
}
|
|
38
|
+
this._gstTrans.direction = w3cDirectionToGst(d);
|
|
39
|
+
}
|
|
40
|
+
get currentDirection() {
|
|
41
|
+
if (this._stopped) return null;
|
|
42
|
+
const cd = this._gstTrans.current_direction ?? this._gstTrans.currentDirection;
|
|
43
|
+
if (cd == null) return null;
|
|
44
|
+
const w3c = gstDirectionToW3C(cd);
|
|
45
|
+
return w3c === "inactive" ? null : w3c;
|
|
46
|
+
}
|
|
47
|
+
get stopped() {
|
|
48
|
+
return this._stopped;
|
|
49
|
+
}
|
|
50
|
+
stop() {
|
|
51
|
+
if (this._stopped) return;
|
|
52
|
+
this._stopped = true;
|
|
53
|
+
}
|
|
54
|
+
setCodecPreferences(codecs) {
|
|
55
|
+
if (!Array.isArray(codecs)) {
|
|
56
|
+
throw new TypeError("codecs must be an array");
|
|
57
|
+
}
|
|
58
|
+
if (codecs.length === 0) {
|
|
59
|
+
this._codecPreferences = [];
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const kind = this.receiver.track.kind;
|
|
63
|
+
const recvCaps = RTCRtpReceiver.getCapabilities(kind);
|
|
64
|
+
const sendCaps = RTCRtpSender.getCapabilities(kind);
|
|
65
|
+
if (!recvCaps || !sendCaps) {
|
|
66
|
+
throw new DOMException("No capabilities available", "InvalidModificationError");
|
|
67
|
+
}
|
|
68
|
+
const allCaps = [...recvCaps.codecs, ...sendCaps.codecs];
|
|
69
|
+
for (const codec of codecs) {
|
|
70
|
+
if (!codec || typeof codec !== "object") {
|
|
71
|
+
throw new TypeError("Each codec must be an object");
|
|
72
|
+
}
|
|
73
|
+
if (typeof codec.mimeType !== "string" || typeof codec.clockRate !== "number") {
|
|
74
|
+
throw new TypeError("codec must have mimeType (string) and clockRate (number)");
|
|
75
|
+
}
|
|
76
|
+
const isResiliency = /\/(rtx|red|ulpfec)$/i.test(codec.mimeType);
|
|
77
|
+
if (isResiliency) continue;
|
|
78
|
+
const match = allCaps.find(
|
|
79
|
+
(c) => c.mimeType.toLowerCase() === codec.mimeType.toLowerCase() && c.clockRate === codec.clockRate && (codec.channels === void 0 || c.channels === codec.channels) && (codec.sdpFmtpLine === void 0 || c.sdpFmtpLine === codec.sdpFmtpLine)
|
|
80
|
+
);
|
|
81
|
+
if (!match) {
|
|
82
|
+
throw new DOMException(
|
|
83
|
+
`Codec ${codec.mimeType} ${codec.clockRate} is not in capabilities`,
|
|
84
|
+
"InvalidModificationError"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
this._codecPreferences = [...codecs];
|
|
89
|
+
}
|
|
90
|
+
/** @internal */
|
|
91
|
+
get _nativeTransceiver() {
|
|
92
|
+
return this._gstTrans;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
RTCRtpTransceiver
|
|
97
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import "@gjsify/dom-events/register/event-target";
|
|
2
|
+
class RTCSctpTransport extends EventTarget {
|
|
3
|
+
transport;
|
|
4
|
+
_state = "connecting";
|
|
5
|
+
_maxMessageSize = 262144;
|
|
6
|
+
// 256 KB default per SCTP
|
|
7
|
+
_maxChannels = 65535;
|
|
8
|
+
_onstatechange = null;
|
|
9
|
+
constructor(dtlsTransport) {
|
|
10
|
+
super();
|
|
11
|
+
this.transport = dtlsTransport;
|
|
12
|
+
}
|
|
13
|
+
get state() {
|
|
14
|
+
return this._state;
|
|
15
|
+
}
|
|
16
|
+
get maxMessageSize() {
|
|
17
|
+
return this._maxMessageSize;
|
|
18
|
+
}
|
|
19
|
+
get maxChannels() {
|
|
20
|
+
return this._maxChannels;
|
|
21
|
+
}
|
|
22
|
+
get onstatechange() {
|
|
23
|
+
return this._onstatechange;
|
|
24
|
+
}
|
|
25
|
+
set onstatechange(v) {
|
|
26
|
+
this._onstatechange = v;
|
|
27
|
+
}
|
|
28
|
+
// ---- Internal setters (called by RTCPeerConnection) ---------------------
|
|
29
|
+
/** @internal */
|
|
30
|
+
_setState(state) {
|
|
31
|
+
if (this._state === state) return;
|
|
32
|
+
this._state = state;
|
|
33
|
+
const ev = new Event("statechange");
|
|
34
|
+
this._onstatechange?.call(this, ev);
|
|
35
|
+
this.dispatchEvent(ev);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
RTCSctpTransport
|
|
40
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import GstSdp from "gi://GstSdp?version=1.0";
|
|
2
|
+
import GstWebRTC from "gi://GstWebRTC?version=1.0";
|
|
3
|
+
function sdpTypeToGst(type) {
|
|
4
|
+
switch (type) {
|
|
5
|
+
case "offer":
|
|
6
|
+
return GstWebRTC.WebRTCSDPType.OFFER;
|
|
7
|
+
case "pranswer":
|
|
8
|
+
return GstWebRTC.WebRTCSDPType.PRANSWER;
|
|
9
|
+
case "answer":
|
|
10
|
+
return GstWebRTC.WebRTCSDPType.ANSWER;
|
|
11
|
+
case "rollback":
|
|
12
|
+
return GstWebRTC.WebRTCSDPType.ROLLBACK;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function sdpTypeFromGst(type) {
|
|
16
|
+
switch (type) {
|
|
17
|
+
case GstWebRTC.WebRTCSDPType.OFFER:
|
|
18
|
+
return "offer";
|
|
19
|
+
case GstWebRTC.WebRTCSDPType.PRANSWER:
|
|
20
|
+
return "pranswer";
|
|
21
|
+
case GstWebRTC.WebRTCSDPType.ANSWER:
|
|
22
|
+
return "answer";
|
|
23
|
+
case GstWebRTC.WebRTCSDPType.ROLLBACK:
|
|
24
|
+
return "rollback";
|
|
25
|
+
default:
|
|
26
|
+
return "offer";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
class RTCSessionDescription {
|
|
30
|
+
type;
|
|
31
|
+
sdp;
|
|
32
|
+
constructor(init) {
|
|
33
|
+
this.type = init?.type ?? "offer";
|
|
34
|
+
this.sdp = init?.sdp ?? "";
|
|
35
|
+
}
|
|
36
|
+
toJSON() {
|
|
37
|
+
return { type: this.type, sdp: this.sdp };
|
|
38
|
+
}
|
|
39
|
+
/** Build a GstWebRTC.WebRTCSessionDescription for use with webrtcbin signals. */
|
|
40
|
+
toGstDesc() {
|
|
41
|
+
const [ret, sdp] = GstSdp.SDPMessage.new_from_text(this.sdp);
|
|
42
|
+
if (ret !== GstSdp.SDPResult.OK) {
|
|
43
|
+
throw new Error(`Failed to parse SDP text (GstSDPResult=${ret})`);
|
|
44
|
+
}
|
|
45
|
+
return GstWebRTC.WebRTCSessionDescription.new(sdpTypeToGst(this.type), sdp);
|
|
46
|
+
}
|
|
47
|
+
static fromGstDesc(desc) {
|
|
48
|
+
const sdpText = desc.sdp?.as_text?.() ?? "";
|
|
49
|
+
return new RTCSessionDescription({
|
|
50
|
+
type: sdpTypeFromGst(desc.type),
|
|
51
|
+
sdp: sdpText
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
RTCSessionDescription
|
|
57
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class RTCStatsReport {
|
|
2
|
+
_map;
|
|
3
|
+
constructor(entries) {
|
|
4
|
+
this._map = new Map(entries);
|
|
5
|
+
}
|
|
6
|
+
get size() {
|
|
7
|
+
return this._map.size;
|
|
8
|
+
}
|
|
9
|
+
get(key) {
|
|
10
|
+
return this._map.get(key);
|
|
11
|
+
}
|
|
12
|
+
has(key) {
|
|
13
|
+
return this._map.has(key);
|
|
14
|
+
}
|
|
15
|
+
forEach(callbackfn, thisArg) {
|
|
16
|
+
this._map.forEach((value, key) => {
|
|
17
|
+
callbackfn.call(thisArg, value, key, this);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
entries() {
|
|
21
|
+
return this._map.entries();
|
|
22
|
+
}
|
|
23
|
+
keys() {
|
|
24
|
+
return this._map.keys();
|
|
25
|
+
}
|
|
26
|
+
values() {
|
|
27
|
+
return this._map.values();
|
|
28
|
+
}
|
|
29
|
+
[Symbol.iterator]() {
|
|
30
|
+
return this._map.entries();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
RTCStatsReport
|
|
35
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "@gjsify/dom-events/register/event-target";
|
|
2
|
+
class RTCTrackEvent extends Event {
|
|
3
|
+
receiver;
|
|
4
|
+
track;
|
|
5
|
+
streams;
|
|
6
|
+
transceiver;
|
|
7
|
+
constructor(type, init) {
|
|
8
|
+
super(type, init);
|
|
9
|
+
if (!init || typeof init !== "object") {
|
|
10
|
+
throw new TypeError("RTCTrackEventInit is required");
|
|
11
|
+
}
|
|
12
|
+
if (!("receiver" in init)) {
|
|
13
|
+
throw new TypeError("Failed to construct 'RTCTrackEvent': required member receiver is not provided.");
|
|
14
|
+
}
|
|
15
|
+
if (!("track" in init)) {
|
|
16
|
+
throw new TypeError("Failed to construct 'RTCTrackEvent': required member track is not provided.");
|
|
17
|
+
}
|
|
18
|
+
if (!("transceiver" in init)) {
|
|
19
|
+
throw new TypeError("Failed to construct 'RTCTrackEvent': required member transceiver is not provided.");
|
|
20
|
+
}
|
|
21
|
+
this.receiver = init.receiver;
|
|
22
|
+
this.track = init.track;
|
|
23
|
+
this.streams = Object.freeze(init.streams ? [...init.streams] : []);
|
|
24
|
+
this.transceiver = init.transceiver;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
RTCTrackEvent
|
|
29
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const AUDIO_CAPABILITIES = {
|
|
2
|
+
codecs: [
|
|
3
|
+
{ mimeType: "audio/opus", clockRate: 48e3, channels: 2, sdpFmtpLine: "minptime=10;useinbandfec=1" },
|
|
4
|
+
{ mimeType: "audio/G722", clockRate: 8e3, channels: 1 },
|
|
5
|
+
{ mimeType: "audio/PCMU", clockRate: 8e3, channels: 1 },
|
|
6
|
+
{ mimeType: "audio/PCMA", clockRate: 8e3, channels: 1 },
|
|
7
|
+
{ mimeType: "audio/telephone-event", clockRate: 8e3, channels: 1 },
|
|
8
|
+
{ mimeType: "audio/red", clockRate: 48e3, channels: 2 }
|
|
9
|
+
],
|
|
10
|
+
headerExtensions: [
|
|
11
|
+
{ uri: "urn:ietf:params:rtp-hdrext:ssrc-audio-level" },
|
|
12
|
+
{ uri: "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" },
|
|
13
|
+
{ uri: "urn:ietf:params:rtp-hdrext:sdes:mid" }
|
|
14
|
+
]
|
|
15
|
+
};
|
|
16
|
+
const VIDEO_CAPABILITIES = {
|
|
17
|
+
codecs: [
|
|
18
|
+
{ mimeType: "video/VP8", clockRate: 9e4 },
|
|
19
|
+
{ mimeType: "video/rtx", clockRate: 9e4 },
|
|
20
|
+
{ mimeType: "video/H264", clockRate: 9e4, sdpFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f" },
|
|
21
|
+
{ mimeType: "video/VP9", clockRate: 9e4 },
|
|
22
|
+
{ mimeType: "video/red", clockRate: 9e4 },
|
|
23
|
+
{ mimeType: "video/ulpfec", clockRate: 9e4 }
|
|
24
|
+
],
|
|
25
|
+
headerExtensions: [
|
|
26
|
+
{ uri: "urn:ietf:params:rtp-hdrext:toffset" },
|
|
27
|
+
{ uri: "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" },
|
|
28
|
+
{ uri: "urn:3gpp:video-orientation" },
|
|
29
|
+
{ uri: "urn:ietf:params:rtp-hdrext:sdes:mid" },
|
|
30
|
+
{ uri: "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id" },
|
|
31
|
+
{ uri: "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id" }
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
function getRtpCapabilities(kind) {
|
|
35
|
+
if (kind === "audio") return AUDIO_CAPABILITIES;
|
|
36
|
+
if (kind === "video") return VIDEO_CAPABILITIES;
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
getRtpCapabilities
|
|
41
|
+
};
|