@gjsify/webrtc 0.4.0 → 0.4.3

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 (44) hide show
  1. package/package.json +73 -70
  2. package/src/get-user-media.ts +0 -131
  3. package/src/gst-enum-maps.ts +0 -125
  4. package/src/gst-init.ts +0 -49
  5. package/src/gst-stats-parser.ts +0 -137
  6. package/src/gst-utils.ts +0 -41
  7. package/src/index.ts +0 -104
  8. package/src/internal/gst-types.ts +0 -122
  9. package/src/media-device-info.ts +0 -33
  10. package/src/media-devices.ts +0 -191
  11. package/src/media-stream-track.ts +0 -159
  12. package/src/media-stream.ts +0 -96
  13. package/src/register/data-channel.ts +0 -11
  14. package/src/register/error.ts +0 -11
  15. package/src/register/media-devices.ts +0 -10
  16. package/src/register/media.ts +0 -15
  17. package/src/register/peer-connection.ts +0 -20
  18. package/src/register.spec.ts +0 -55
  19. package/src/register.ts +0 -10
  20. package/src/rtc-certificate.ts +0 -110
  21. package/src/rtc-data-channel.ts +0 -283
  22. package/src/rtc-dtls-transport.ts +0 -48
  23. package/src/rtc-dtmf-sender.ts +0 -146
  24. package/src/rtc-error.ts +0 -49
  25. package/src/rtc-events.ts +0 -64
  26. package/src/rtc-ice-candidate.ts +0 -115
  27. package/src/rtc-ice-transport.ts +0 -104
  28. package/src/rtc-peer-connection.ts +0 -1039
  29. package/src/rtc-rtp-receiver.ts +0 -122
  30. package/src/rtc-rtp-sender.ts +0 -471
  31. package/src/rtc-rtp-transceiver.ts +0 -131
  32. package/src/rtc-sctp-transport.ts +0 -48
  33. package/src/rtc-session-description.ts +0 -64
  34. package/src/rtc-stats-report.ts +0 -39
  35. package/src/rtc-track-event.ts +0 -45
  36. package/src/rtp-capabilities.ts +0 -48
  37. package/src/tee-multiplexer.ts +0 -75
  38. package/src/test.mts +0 -11
  39. package/src/webrtc.spec.ts +0 -1186
  40. package/src/wpt-helpers.ts +0 -156
  41. package/src/wpt-media.spec.ts +0 -1154
  42. package/src/wpt.spec.ts +0 -1136
  43. package/tsconfig.json +0 -36
  44. package/tsconfig.tsbuildinfo +0 -1
package/src/index.ts DELETED
@@ -1,104 +0,0 @@
1
- // W3C WebRTC API for GJS — backed by GStreamer webrtcbin.
2
- //
3
- // This module has no side effects. Importing @gjsify/webrtc gives named
4
- // access to the classes but does NOT register globals. Use
5
- // @gjsify/webrtc/register (or a granular subpath) to set globalThis.RTCPeerConnection etc.
6
-
7
- export { RTCPeerConnection } from './rtc-peer-connection.js';
8
- export type {
9
- RTCConfiguration,
10
- RTCIceServer,
11
- RTCOfferOptions,
12
- RTCAnswerOptions,
13
- RTCDataChannelInit,
14
- RTCSignalingState,
15
- RTCPeerConnectionState,
16
- RTCIceConnectionState,
17
- RTCIceGatheringState,
18
- RTCIceTransportPolicy,
19
- RTCBundlePolicy,
20
- RTCRtcpMuxPolicy,
21
- } from './rtc-peer-connection.js';
22
-
23
- export { RTCDataChannel } from './rtc-data-channel.js';
24
- export type { RTCDataChannelState, BinaryType } from './rtc-data-channel.js';
25
-
26
- export { RTCSessionDescription } from './rtc-session-description.js';
27
- export type { RTCSessionDescriptionInit, RTCSdpType } from './rtc-session-description.js';
28
-
29
- export { RTCIceCandidate } from './rtc-ice-candidate.js';
30
- export type {
31
- RTCIceCandidateInit,
32
- RTCIceComponent,
33
- RTCIceProtocol,
34
- RTCIceCandidateType,
35
- RTCIceTcpCandidateType,
36
- } from './rtc-ice-candidate.js';
37
-
38
- export { RTCError } from './rtc-error.js';
39
- export type { RTCErrorInit, RTCErrorDetailType } from './rtc-error.js';
40
-
41
- export {
42
- RTCPeerConnectionIceEvent,
43
- RTCDataChannelEvent,
44
- RTCErrorEvent,
45
- } from './rtc-events.js';
46
- export type {
47
- RTCPeerConnectionIceEventInit,
48
- RTCDataChannelEventInit,
49
- RTCErrorEventInit,
50
- } from './rtc-events.js';
51
-
52
- export { RTCRtpSender } from './rtc-rtp-sender.js';
53
- export type {
54
- RTCRtpTransceiverDirection,
55
- RTCRtpCapabilities,
56
- RTCRtpCodecCapability,
57
- RTCRtpHeaderExtensionCapability,
58
- RTCRtpSendParameters,
59
- RTCRtpEncodingParameters,
60
- RTCRtpCodecParameters,
61
- RTCRtpHeaderExtensionParameters,
62
- RTCRtcpParameters,
63
- } from './rtc-rtp-sender.js';
64
-
65
- export { RTCRtpReceiver } from './rtc-rtp-receiver.js';
66
- export type { RTCRtpReceiveParameters } from './rtc-rtp-receiver.js';
67
-
68
- export { RTCRtpTransceiver } from './rtc-rtp-transceiver.js';
69
-
70
- export { MediaStream } from './media-stream.js';
71
- export { MediaStreamTrackEvent } from './media-stream.js';
72
-
73
- export { MediaStreamTrack } from './media-stream-track.js';
74
- export type { MediaStreamTrackInit } from './media-stream-track.js';
75
-
76
- export { RTCTrackEvent } from './rtc-track-event.js';
77
- export type { RTCTrackEventInit } from './rtc-track-event.js';
78
-
79
- export type { RTCRtpTransceiverInit } from './rtc-peer-connection.js';
80
-
81
- export { getUserMedia } from './get-user-media.js';
82
- export type { MediaStreamConstraints, MediaTrackConstraints } from './get-user-media.js';
83
-
84
- export { MediaDevices } from './media-devices.js';
85
- export { MediaDeviceInfo } from './media-device-info.js';
86
- export type { MediaDeviceKind } from './media-device-info.js';
87
-
88
- export { RTCStatsReport } from './rtc-stats-report.js';
89
- export type { RTCStats } from './rtc-stats-report.js';
90
-
91
- export { RTCDtlsTransport } from './rtc-dtls-transport.js';
92
- export type { RTCDtlsTransportState } from './rtc-dtls-transport.js';
93
-
94
- export { RTCIceTransport } from './rtc-ice-transport.js';
95
- export type { RTCIceTransportState, RTCIceRole, RTCIceParameters, RTCIceCandidatePair } from './rtc-ice-transport.js';
96
-
97
- export { RTCSctpTransport } from './rtc-sctp-transport.js';
98
- export type { RTCSctpTransportState } from './rtc-sctp-transport.js';
99
-
100
- export { RTCDTMFSender, RTCDTMFToneChangeEvent } from './rtc-dtmf-sender.js';
101
- export type { RTCDTMFToneChangeEventInit } from './rtc-dtmf-sender.js';
102
-
103
- export { RTCCertificate } from './rtc-certificate.js';
104
- export type { RTCDtlsFingerprint, AlgorithmIdentifier } from './rtc-certificate.js';
@@ -1,122 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- // Internal type helpers for @gjsify/webrtc — narrowing the broad
3
- // Gst.Element / Gst.Pad surface to the concrete element shapes our
4
- // implementation reads and writes at runtime.
5
- //
6
- // Background: `@girs/gst-1.0` declares every GStreamer element as the
7
- // base `Gst.Element` class — element-specific GObject properties (e.g.
8
- // `webrtcbin`'s `stun_server`, `signaling_state`, or `vp8enc`'s
9
- // `keyframe_max_dist`) are not exposed in the GIR-generated typings
10
- // because they are registered at element-class init time, not on the
11
- // base class. Rather than reach for `(el as any).foo` at every call
12
- // site, we declare thin interfaces here and narrow once through helper
13
- // casts.
14
- //
15
- // These types are PURE compile-time constructs — no runtime behavior is
16
- // added. Following AGENTS.md Rule 2c, this module lives under
17
- // `src/internal/` and is NOT in `package.json#exports`; it is a private
18
- // implementation helper, not part of the public API surface.
19
- //
20
- // References:
21
- // - GStreamer webrtcbin element properties:
22
- // https://gstreamer.freedesktop.org/documentation/webrtc/index.html
23
- // - vp8enc / opusenc / capsfilter / valve / payloader properties:
24
- // https://gstreamer.freedesktop.org/documentation/
25
-
26
- import type Gst from 'gi://Gst?version=1.0';
27
- import type GstWebRTC from 'gi://GstWebRTC?version=1.0';
28
-
29
- /**
30
- * The `webrtcbin` GStreamer element — declares every GObject property
31
- * our implementation accesses. The base `Gst.Element` class is preserved
32
- * so all the inherited methods (`emit`, `get_parent`, `set_state`,
33
- * `request_pad_simple`, `sync_state_with_parent`, …) keep their proper
34
- * typings.
35
- *
36
- * All properties below correspond to runtime GObject properties on
37
- * `webrtcbin` (verify with `gst-inspect-1.0 webrtcbin`).
38
- */
39
- export interface WebRtcBin extends Gst.Element {
40
- stun_server: string | null;
41
- turn_server: string | null;
42
- ice_transport_policy: GstWebRTC.WebRTCICETransportPolicy;
43
- bundle_policy: GstWebRTC.WebRTCBundlePolicy;
44
- signaling_state: GstWebRTC.WebRTCSignalingState;
45
- connection_state: GstWebRTC.WebRTCPeerConnectionState;
46
- ice_connection_state: GstWebRTC.WebRTCICEConnectionState;
47
- ice_gathering_state: GstWebRTC.WebRTCICEGatheringState;
48
- local_description: GstWebRTC.WebRTCSessionDescription | null;
49
- remote_description: GstWebRTC.WebRTCSessionDescription | null;
50
- current_local_description: GstWebRTC.WebRTCSessionDescription | null;
51
- current_remote_description: GstWebRTC.WebRTCSessionDescription | null;
52
- pending_local_description: GstWebRTC.WebRTCSessionDescription | null;
53
- pending_remote_description: GstWebRTC.WebRTCSessionDescription | null;
54
- }
55
-
56
- /**
57
- * Narrow a `Gst.Element` returned by `Gst.ElementFactory.make('webrtcbin', …)`
58
- * to the augmented `WebRtcBin` shape. Pure type-level cast — no runtime
59
- * validation is performed because every webrtcbin instance has these
60
- * properties (they are class-installed by GstWebRTCBin).
61
- */
62
- export const asWebRtcBin = (el: Gst.Element): WebRtcBin => el as WebRtcBin;
63
-
64
- /**
65
- * GStreamer pad augmented with the `transceiver` field that webrtcbin's
66
- * SRC pads carry. The base `Gst.Pad` typing has no concept of this — it
67
- * is set at pad-creation time inside webrtcbin and points back to the
68
- * `GstWebRTCRTPTransceiver` the pad serves.
69
- */
70
- export interface WebRtcSrcPad extends Gst.Pad {
71
- transceiver: GstWebRTC.WebRTCRTPTransceiver | null;
72
- }
73
-
74
- /** Narrow a webrtcbin SRC `Gst.Pad` to the augmented shape. */
75
- export const asWebRtcSrcPad = (pad: Gst.Pad): WebRtcSrcPad => pad as WebRtcSrcPad;
76
-
77
- /**
78
- * GStreamer element with a settable `drop` GObject property — the
79
- * `valve` element used to gate media flow when a track is disabled.
80
- */
81
- export interface ValveElement extends Gst.Element {
82
- drop: boolean;
83
- }
84
-
85
- /** Narrow a `valve` element to the augmented shape. */
86
- export const asValveElement = (el: Gst.Element): ValveElement => el as ValveElement;
87
-
88
- /**
89
- * GStreamer element with a settable `pt` GObject property — RTP payloader
90
- * elements (`rtpopuspay`, `rtpvp8pay`, …) accept the payload type.
91
- */
92
- export interface RtpPayloaderElement extends Gst.Element {
93
- pt: number;
94
- }
95
-
96
- /** Narrow an RTP payloader element to the augmented shape. */
97
- export const asRtpPayloaderElement = (el: Gst.Element): RtpPayloaderElement =>
98
- el as RtpPayloaderElement;
99
-
100
- /**
101
- * GStreamer `capsfilter` element — exposes a settable `caps` property
102
- * that pins the negotiated caps of a pipeline branch.
103
- */
104
- export interface CapsFilterElement extends Gst.Element {
105
- caps: Gst.Caps;
106
- }
107
-
108
- /** Narrow a `capsfilter` element to the augmented shape. */
109
- export const asCapsFilterElement = (el: Gst.Element): CapsFilterElement =>
110
- el as CapsFilterElement;
111
-
112
- /**
113
- * GStreamer `vp8enc` encoder — the small set of properties we tune for
114
- * realtime WebRTC video encoding.
115
- */
116
- export interface Vp8EncElement extends Gst.Element {
117
- deadline: number;
118
- keyframe_max_dist: number;
119
- }
120
-
121
- /** Narrow a `vp8enc` element to the augmented shape. */
122
- export const asVp8EncElement = (el: Gst.Element): Vp8EncElement => el as Vp8EncElement;
@@ -1,33 +0,0 @@
1
- // W3C MediaDeviceInfo for GJS — backed by GStreamer Device Monitor.
2
- //
3
- // Reference: W3C Media Capture and Streams spec § 10.2.1
4
-
5
- export type MediaDeviceKind = 'audioinput' | 'audiooutput' | 'videoinput';
6
-
7
- export class MediaDeviceInfo {
8
- readonly deviceId: string;
9
- readonly kind: MediaDeviceKind;
10
- readonly label: string;
11
- readonly groupId: string;
12
-
13
- constructor(init: {
14
- deviceId: string;
15
- kind: MediaDeviceKind;
16
- label: string;
17
- groupId?: string;
18
- }) {
19
- this.deviceId = init.deviceId;
20
- this.kind = init.kind;
21
- this.label = init.label;
22
- this.groupId = init.groupId ?? '';
23
- }
24
-
25
- toJSON(): object {
26
- return {
27
- deviceId: this.deviceId,
28
- kind: this.kind,
29
- label: this.label,
30
- groupId: this.groupId,
31
- };
32
- }
33
- }
@@ -1,191 +0,0 @@
1
- // W3C MediaDevices for GJS.
2
- //
3
- // Phase 3: getUserMedia via GStreamer sources.
4
- // Phase 4.3: enumerateDevices via GStreamer Device Monitor,
5
- // getSupportedConstraints returns supported constraints.
6
- //
7
- // Reference: W3C Media Capture and Streams spec § 10.2
8
- // Reference: refs/webkit/Source/WebCore/platform/mediastream/gstreamer/GStreamerCaptureDeviceManager.cpp
9
-
10
- import '@gjsify/dom-events/register/event-target';
11
-
12
- import { ensureGstInit, Gst } from './gst-init.js';
13
- import { getUserMedia, type MediaStreamConstraints } from './get-user-media.js';
14
- import { MediaDeviceInfo, type MediaDeviceKind } from './media-device-info.js';
15
- import type { MediaStream } from './media-stream.js';
16
-
17
- /** Map GStreamer device class strings to W3C MediaDeviceKind. */
18
- const DEVICE_CLASS_MAP: Record<string, MediaDeviceKind> = {
19
- 'Audio/Source': 'audioinput',
20
- 'Video/Source': 'videoinput',
21
- 'Audio/Sink': 'audiooutput',
22
- };
23
-
24
- /** Whether getUserMedia has been successfully called (unlocks full device info). */
25
- let _permissionGranted = false;
26
-
27
- /**
28
- * Check if GStreamer device monitoring is safe to use.
29
- * On some GJS/GStreamer combinations (e.g. Fedora 44 / GJS 1.88 in Docker),
30
- * DeviceMonitor and DeviceProviderFactory can SIGSEGV in native code — a crash
31
- * that JS error handling cannot intercept. We skip device monitoring entirely
32
- * when DISPLAY is absent (headless/CI) since there are typically no audio/video
33
- * devices in containers anyway.
34
- */
35
- function isDeviceMonitorSafe(): boolean {
36
- try {
37
- // Import GLib to check environment — avoid crashing GStreamer APIs
38
- const GLib = imports.gi.GLib;
39
- // Skip in CI environments or headless containers
40
- if (GLib.getenv('CI')) return false;
41
- // No display = likely a container without devices
42
- if (!GLib.getenv('DISPLAY') && !GLib.getenv('WAYLAND_DISPLAY')) return false;
43
- return true;
44
- } catch {
45
- return false;
46
- }
47
- }
48
-
49
- export class MediaDevices extends EventTarget {
50
- private _ondevicechange: ((ev: Event) => void) | null = null;
51
-
52
- get ondevicechange(): ((ev: Event) => void) | null { return this._ondevicechange; }
53
- set ondevicechange(v: ((ev: Event) => void) | null) { this._ondevicechange = v; }
54
-
55
- async getUserMedia(constraints?: MediaStreamConstraints): Promise<MediaStream> {
56
- if (!constraints) {
57
- throw new TypeError(
58
- "Failed to execute 'getUserMedia' on 'MediaDevices': At least one of audio or video must be requested",
59
- );
60
- }
61
- const stream = await getUserMedia(constraints);
62
- _permissionGranted = true;
63
- return stream;
64
- }
65
-
66
- async enumerateDevices(): Promise<MediaDeviceInfo[]> {
67
- ensureGstInit();
68
-
69
- let monitor: InstanceType<typeof Gst.DeviceMonitor> | null = null;
70
- const result: MediaDeviceInfo[] = [];
71
-
72
- try {
73
- // Guard: on CI containers without PipeWire/PulseAudio, DeviceMonitor
74
- // can SIGSEGV in native GStreamer code. Check for device providers first.
75
- if (!isDeviceMonitorSafe()) {
76
- return result;
77
- }
78
-
79
- monitor = new Gst.DeviceMonitor();
80
- monitor.set_show_all_devices(true);
81
- const audioCaps = Gst.Caps.from_string('audio/x-raw');
82
- const videoCaps = Gst.Caps.from_string('video/x-raw');
83
- if (audioCaps) {
84
- monitor.add_filter('Audio/Source', audioCaps);
85
- monitor.add_filter('Audio/Sink', audioCaps);
86
- }
87
- if (videoCaps) {
88
- monitor.add_filter('Video/Source', videoCaps);
89
- }
90
-
91
- if (!monitor.start()) {
92
- // DeviceMonitor failed to start — return empty list gracefully
93
- return result;
94
- }
95
-
96
- let gstDevices: any[];
97
- try {
98
- gstDevices = monitor.get_devices() ?? [];
99
- } catch {
100
- // get_devices() can crash on some GStreamer/GJS versions — return empty
101
- return result;
102
- }
103
-
104
- for (const device of gstDevices) {
105
- const deviceClass = device.get_device_class?.() ?? '';
106
- const kind = DEVICE_CLASS_MAP[deviceClass];
107
- if (!kind) continue;
108
-
109
- const displayName = device.get_display_name?.() ?? '';
110
- let deviceId = '';
111
- let groupId = '';
112
-
113
- // Extract persistent-id from device properties if available
114
- try {
115
- const props = device.get_properties?.();
116
- if (props) {
117
- const n = props.n_fields();
118
- for (let i = 0; i < n; i++) {
119
- const name = props.nth_field_name(i);
120
- if (name === 'persistent-id' || name === 'node.name') {
121
- const val = props.get_value(name);
122
- if (val && !deviceId) deviceId = String(val);
123
- }
124
- if (name === 'group-id') {
125
- const val = props.get_value(name);
126
- if (val) groupId = String(val);
127
- }
128
- }
129
- }
130
- } catch { /* properties may not be available */ }
131
-
132
- // Fallback deviceId from display name hash
133
- if (!deviceId) {
134
- deviceId = displayName || `${kind}-${result.length}`;
135
- }
136
-
137
- // Per W3C: before getUserMedia permission, expose only empty
138
- // deviceId/label/groupId (one device per kind max).
139
- if (_permissionGranted) {
140
- result.push(new MediaDeviceInfo({
141
- deviceId,
142
- kind,
143
- label: displayName,
144
- groupId,
145
- }));
146
- } else {
147
- // Check if we already have a device of this kind
148
- if (!result.some(d => d.kind === kind)) {
149
- result.push(new MediaDeviceInfo({
150
- deviceId: '',
151
- kind,
152
- label: '',
153
- groupId: '',
154
- }));
155
- }
156
- }
157
- }
158
- } catch {
159
- // DeviceMonitor or device enumeration crashed — return whatever we have
160
- return result;
161
- } finally {
162
- try { monitor?.stop(); } catch { /* ignore stop errors */ }
163
- }
164
-
165
- // W3C ordering: audioinput first, then videoinput, then audiooutput
166
- const order: Record<string, number> = { audioinput: 0, videoinput: 1, audiooutput: 2 };
167
- result.sort((a, b) => (order[a.kind] ?? 3) - (order[b.kind] ?? 3));
168
-
169
- return result;
170
- }
171
-
172
- getSupportedConstraints(): Record<string, boolean> {
173
- return {
174
- deviceId: true,
175
- width: true,
176
- height: true,
177
- frameRate: true,
178
- sampleRate: true,
179
- channelCount: true,
180
- // Not yet supported — return false
181
- aspectRatio: false,
182
- facingMode: false,
183
- resizeMode: false,
184
- echoCancellation: false,
185
- autoGainControl: false,
186
- noiseSuppression: false,
187
- latency: false,
188
- groupId: false,
189
- };
190
- }
191
- }
@@ -1,159 +0,0 @@
1
- // W3C MediaStreamTrack for GJS.
2
- //
3
- // Phase 2: lightweight API surface with event dispatch.
4
- // Phase 3: optional GStreamer source integration — tracks created by
5
- // getUserMedia carry a GStreamer source element reference that the
6
- // RTCRtpSender wires into the webrtcbin pipeline.
7
- //
8
- // Reference: refs/node-gst-webrtc/src/media/MediaStreamTrack.ts (ISC)
9
- // Reference: W3C MediaStreamTrack spec
10
-
11
- import '@gjsify/dom-events/register/event-target';
12
-
13
- import GLib from 'gi://GLib?version=2.0';
14
-
15
- import { Gst } from './gst-init.js';
16
-
17
- /** @internal GStreamer backing for tracks created by getUserMedia */
18
- export interface MediaStreamTrackGstInit {
19
- source: any; // Gst.Element
20
- pipeline: any; // Gst.Pipeline
21
- }
22
-
23
- export interface MediaStreamTrackInit {
24
- kind: 'audio' | 'video';
25
- label?: string;
26
- id?: string;
27
- muted?: boolean;
28
- /** @internal */
29
- _gst?: MediaStreamTrackGstInit;
30
- }
31
-
32
- export class MediaStreamTrack extends EventTarget {
33
- readonly id: string;
34
- readonly kind: 'audio' | 'video';
35
- readonly label: string;
36
-
37
- private _enabled = true;
38
- private _muted: boolean;
39
- private _ended = false;
40
- private _contentHint = '';
41
-
42
- private _onended: ((ev: Event) => void) | null = null;
43
- private _onmute: ((ev: Event) => void) | null = null;
44
- private _onunmute: ((ev: Event) => void) | null = null;
45
-
46
- /** @internal GStreamer source element (e.g. pulsesrc, audiotestsrc) */
47
- _gstSource: any = null;
48
- /** @internal Pipeline the source currently lives in (updated by VideoBridge) */
49
- _gstPipeline: any = null;
50
- /** @internal Tee element inserted by VideoBridge for preview fan-out */
51
- _gstTee: any = null;
52
- /** @internal TeeMultiplexer for multi-PC fan-out (created on second addTrack) */
53
- _teeMultiplexer: any = null;
54
- /** @internal Callback set by RTCRtpSender to control valve drop property */
55
- private _enableCallback: ((enabled: boolean) => void) | null = null;
56
-
57
- constructor(init: MediaStreamTrackInit) {
58
- super();
59
- this.id = init.id ?? GLib.uuid_string_random();
60
- this.kind = init.kind;
61
- this.label = init.label ?? '';
62
- this._muted = init.muted ?? false;
63
-
64
- if (init._gst) {
65
- this._gstSource = init._gst.source;
66
- this._gstPipeline = init._gst.pipeline;
67
- }
68
- }
69
-
70
- get enabled(): boolean { return this._enabled; }
71
- set enabled(v: boolean) {
72
- const val = !!v;
73
- if (this._enabled === val) return;
74
- this._enabled = val;
75
- this._enableCallback?.(val);
76
- }
77
-
78
- get muted(): boolean { return this._muted; }
79
-
80
- get readyState(): 'live' | 'ended' { return this._ended ? 'ended' : 'live'; }
81
-
82
- get contentHint(): string { return this._contentHint; }
83
- set contentHint(v: string) {
84
- if (this.kind === 'audio') {
85
- if (v !== '' && v !== 'speech' && v !== 'speech-recognition' && v !== 'music') return;
86
- } else {
87
- if (v !== '' && v !== 'motion' && v !== 'detail' && v !== 'text') return;
88
- }
89
- this._contentHint = v;
90
- }
91
-
92
- get onended(): ((ev: Event) => void) | null { return this._onended; }
93
- set onended(v: ((ev: Event) => void) | null) { this._onended = v; }
94
- get onmute(): ((ev: Event) => void) | null { return this._onmute; }
95
- set onmute(v: ((ev: Event) => void) | null) { this._onmute = v; }
96
- get onunmute(): ((ev: Event) => void) | null { return this._onunmute; }
97
- set onunmute(v: ((ev: Event) => void) | null) { this._onunmute = v; }
98
-
99
- clone(): MediaStreamTrack {
100
- const cloned = new MediaStreamTrack({
101
- kind: this.kind,
102
- label: this.label,
103
- muted: this._muted,
104
- });
105
- cloned._enabled = this._enabled;
106
- return cloned;
107
- }
108
-
109
- stop(): void {
110
- if (this._ended) return;
111
- this._ended = true;
112
-
113
- // Clean up GStreamer source and pipeline if present
114
- if (this._gstSource || this._gstPipeline) {
115
- try {
116
- // Set pipeline to NULL first (this stops all children)
117
- this._gstPipeline?.set_state(Gst.State.NULL);
118
- } catch { /* ignore */ }
119
- try {
120
- this._gstSource?.set_state(Gst.State.NULL);
121
- } catch { /* ignore */ }
122
- this._gstSource = null;
123
- this._gstPipeline = null;
124
- }
125
-
126
- const ev = new Event('ended');
127
- this._onended?.call(this, ev);
128
- this.dispatchEvent(ev);
129
- }
130
-
131
- getCapabilities(): Record<string, unknown> { return {}; }
132
- getConstraints(): Record<string, unknown> { return {}; }
133
- getSettings(): Record<string, unknown> { return {}; }
134
-
135
- applyConstraints(_constraints?: unknown): Promise<void> {
136
- return Promise.reject(new DOMException(
137
- 'applyConstraints is not supported',
138
- 'NotSupportedError',
139
- ));
140
- }
141
-
142
- /** @internal — used by RTCRtpReceiver to toggle mute state */
143
- _setMuted(muted: boolean): void {
144
- if (this._muted === muted) return;
145
- this._muted = muted;
146
- const ev = new Event(muted ? 'mute' : 'unmute');
147
- if (muted) {
148
- this._onmute?.call(this, ev);
149
- } else {
150
- this._onunmute?.call(this, ev);
151
- }
152
- this.dispatchEvent(ev);
153
- }
154
-
155
- /** @internal — called by RTCRtpSender to wire valve control */
156
- _setEnableCallback(cb: ((enabled: boolean) => void) | null): void {
157
- this._enableCallback = cb;
158
- }
159
- }