@agentdance/node-webrtc 1.0.0
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/dist/data-channel.d.ts +33 -0
- package/dist/data-channel.d.ts.map +1 -0
- package/dist/data-channel.js +138 -0
- package/dist/data-channel.js.map +1 -0
- package/dist/ice-candidate.d.ts +30 -0
- package/dist/ice-candidate.d.ts.map +1 -0
- package/dist/ice-candidate.js +78 -0
- package/dist/ice-candidate.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/peer-internals.d.ts +55 -0
- package/dist/internal/peer-internals.d.ts.map +1 -0
- package/dist/internal/peer-internals.js +412 -0
- package/dist/internal/peer-internals.js.map +1 -0
- package/dist/internal/sdp-factory.d.ts +9 -0
- package/dist/internal/sdp-factory.d.ts.map +1 -0
- package/dist/internal/sdp-factory.js +58 -0
- package/dist/internal/sdp-factory.js.map +1 -0
- package/dist/internal/sdp-parser.d.ts +15 -0
- package/dist/internal/sdp-parser.d.ts.map +1 -0
- package/dist/internal/sdp-parser.js +70 -0
- package/dist/internal/sdp-parser.js.map +1 -0
- package/dist/peer-connection.d.ts +72 -0
- package/dist/peer-connection.d.ts.map +1 -0
- package/dist/peer-connection.js +198 -0
- package/dist/peer-connection.js.map +1 -0
- package/dist/rtp-receiver.d.ts +28 -0
- package/dist/rtp-receiver.d.ts.map +1 -0
- package/dist/rtp-receiver.js +39 -0
- package/dist/rtp-receiver.js.map +1 -0
- package/dist/rtp-sender.d.ts +29 -0
- package/dist/rtp-sender.d.ts.map +1 -0
- package/dist/rtp-sender.js +58 -0
- package/dist/rtp-sender.js.map +1 -0
- package/dist/rtp-transceiver.d.ts +25 -0
- package/dist/rtp-transceiver.d.ts.map +1 -0
- package/dist/rtp-transceiver.js +28 -0
- package/dist/rtp-transceiver.js.map +1 -0
- package/dist/session-description.d.ts +8 -0
- package/dist/session-description.d.ts.map +1 -0
- package/dist/session-description.js +12 -0
- package/dist/session-description.js.map +1 -0
- package/dist/stats.d.ts +16 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +22 -0
- package/dist/stats.js.map +1 -0
- package/dist/types.d.ts +113 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +68 -0
- package/src/data-channel.ts +165 -0
- package/src/ice-candidate.ts +91 -0
- package/src/index.ts +8 -0
- package/src/internal/peer-internals.ts +477 -0
- package/src/internal/sdp-factory.ts +81 -0
- package/src/internal/sdp-parser.ts +69 -0
- package/src/peer-connection.ts +253 -0
- package/src/rtp-receiver.ts +51 -0
- package/src/rtp-sender.ts +77 -0
- package/src/rtp-transceiver.ts +40 -0
- package/src/session-description.ts +15 -0
- package/src/stats.ts +35 -0
- package/src/types.ts +156 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export type RTCSignalingState = 'stable' | 'have-local-offer' | 'have-remote-offer' | 'have-local-pranswer' | 'have-remote-pranswer' | 'closed';
|
|
2
|
+
export type RTCPeerConnectionState = 'new' | 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed';
|
|
3
|
+
export type RTCIceConnectionState = 'new' | 'checking' | 'connected' | 'completed' | 'failed' | 'disconnected' | 'closed';
|
|
4
|
+
export type RTCIceGatheringState = 'new' | 'gathering' | 'complete';
|
|
5
|
+
export type RTCSdpType = 'offer' | 'pranswer' | 'answer' | 'rollback';
|
|
6
|
+
export interface RTCSessionDescriptionInit {
|
|
7
|
+
type: RTCSdpType;
|
|
8
|
+
sdp: string;
|
|
9
|
+
}
|
|
10
|
+
export interface RTCSessionDescription {
|
|
11
|
+
type: RTCSdpType;
|
|
12
|
+
sdp: string;
|
|
13
|
+
}
|
|
14
|
+
export interface RTCIceCandidateInit {
|
|
15
|
+
candidate: string;
|
|
16
|
+
sdpMid?: string;
|
|
17
|
+
sdpMLineIndex?: number;
|
|
18
|
+
usernameFragment?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface RTCIceServer {
|
|
21
|
+
urls: string | string[];
|
|
22
|
+
username?: string;
|
|
23
|
+
credential?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface RTCConfiguration {
|
|
26
|
+
iceServers?: RTCIceServer[];
|
|
27
|
+
iceTransportPolicy?: 'all' | 'relay';
|
|
28
|
+
bundlePolicy?: 'balanced' | 'max-bundle' | 'max-compat';
|
|
29
|
+
rtcpMuxPolicy?: 'require';
|
|
30
|
+
iceCandidatePoolSize?: number;
|
|
31
|
+
certificates?: RTCDtlsTransportCertificate[];
|
|
32
|
+
}
|
|
33
|
+
export interface RTCDtlsTransportCertificate {
|
|
34
|
+
fingerprint: {
|
|
35
|
+
algorithm: string;
|
|
36
|
+
value: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface RTCOfferOptions {
|
|
40
|
+
iceRestart?: boolean;
|
|
41
|
+
offerToReceiveAudio?: boolean;
|
|
42
|
+
offerToReceiveVideo?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface RTCAnswerOptions {
|
|
45
|
+
}
|
|
46
|
+
export type RTCRtpTransceiverDirection = 'sendrecv' | 'sendonly' | 'recvonly' | 'inactive' | 'stopped';
|
|
47
|
+
export interface RTCRtpCodecParameters {
|
|
48
|
+
mimeType: string;
|
|
49
|
+
clockRate: number;
|
|
50
|
+
channels?: number;
|
|
51
|
+
sdpFmtpLine?: string;
|
|
52
|
+
payloadType: number;
|
|
53
|
+
}
|
|
54
|
+
export interface RTCRtpHeaderExtensionParameters {
|
|
55
|
+
uri: string;
|
|
56
|
+
id: number;
|
|
57
|
+
encrypted?: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface RTCRtpParameters {
|
|
60
|
+
codecs: RTCRtpCodecParameters[];
|
|
61
|
+
headerExtensions: RTCRtpHeaderExtensionParameters[];
|
|
62
|
+
}
|
|
63
|
+
export interface RTCRtpSendParameters extends RTCRtpParameters {
|
|
64
|
+
encodings: RTCRtpEncodingParameters[];
|
|
65
|
+
transactionId: string;
|
|
66
|
+
}
|
|
67
|
+
export interface RTCRtpReceiveParameters extends RTCRtpParameters {
|
|
68
|
+
}
|
|
69
|
+
export interface RTCRtpEncodingParameters {
|
|
70
|
+
rid?: string;
|
|
71
|
+
active?: boolean;
|
|
72
|
+
maxBitrate?: number;
|
|
73
|
+
scaleResolutionDownBy?: number;
|
|
74
|
+
ssrc?: number;
|
|
75
|
+
}
|
|
76
|
+
export interface RTCSctpTransportInit {
|
|
77
|
+
maxMessageSize: number;
|
|
78
|
+
maxChannels: number;
|
|
79
|
+
state: 'new' | 'connecting' | 'connected' | 'closed';
|
|
80
|
+
}
|
|
81
|
+
export type RTCDataChannelState = 'connecting' | 'open' | 'closing' | 'closed';
|
|
82
|
+
export interface RTCDataChannelInit {
|
|
83
|
+
ordered?: boolean;
|
|
84
|
+
maxPacketLifeTime?: number;
|
|
85
|
+
maxRetransmits?: number;
|
|
86
|
+
protocol?: string;
|
|
87
|
+
negotiated?: boolean;
|
|
88
|
+
id?: number;
|
|
89
|
+
priority?: 'very-low' | 'low' | 'medium' | 'high';
|
|
90
|
+
}
|
|
91
|
+
export interface RTCStats {
|
|
92
|
+
id: string;
|
|
93
|
+
type: string;
|
|
94
|
+
timestamp: number;
|
|
95
|
+
}
|
|
96
|
+
export interface RTCIceCandidatePairStats extends RTCStats {
|
|
97
|
+
type: 'candidate-pair';
|
|
98
|
+
localCandidateId: string;
|
|
99
|
+
remoteCandidateId: string;
|
|
100
|
+
state: string;
|
|
101
|
+
nominated: boolean;
|
|
102
|
+
bytesSent: number;
|
|
103
|
+
bytesReceived: number;
|
|
104
|
+
totalRoundTripTime: number;
|
|
105
|
+
currentRoundTripTime?: number;
|
|
106
|
+
}
|
|
107
|
+
export interface RTCStatsReport {
|
|
108
|
+
entries(): IterableIterator<[string, RTCStats]>;
|
|
109
|
+
get(id: string): RTCStats | undefined;
|
|
110
|
+
has(id: string): boolean;
|
|
111
|
+
forEach(callbackfn: (value: RTCStats, key: string) => void): void;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentdance/node-webrtc",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-grade WebRTC in pure TypeScript — RTCPeerConnection, DataChannel, ICE, DTLS, SCTP, SRTP. Zero native bindings.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"webrtc",
|
|
7
|
+
"rtcpeerconnection",
|
|
8
|
+
"datachannel",
|
|
9
|
+
"ice",
|
|
10
|
+
"dtls",
|
|
11
|
+
"sctp",
|
|
12
|
+
"srtp",
|
|
13
|
+
"p2p",
|
|
14
|
+
"typescript",
|
|
15
|
+
"node"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"homepage": "https://github.com/agent-dance/node-webrtc#readme",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/agent-dance/node-webrtc.git",
|
|
22
|
+
"directory": "packages/webrtc"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/agent-dance/node-webrtc/issues"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"import": "./dist/index.js",
|
|
36
|
+
"types": "./dist/index.d.ts"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"src"
|
|
42
|
+
],
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"registry": "https://registry.npmjs.org/"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@agentdance/node-webrtc-sdp": "1.0.0",
|
|
50
|
+
"@agentdance/node-webrtc-ice": "1.0.0",
|
|
51
|
+
"@agentdance/node-webrtc-dtls": "1.0.0",
|
|
52
|
+
"@agentdance/node-webrtc-srtp": "1.0.0",
|
|
53
|
+
"@agentdance/node-webrtc-rtp": "1.0.0",
|
|
54
|
+
"@agentdance/node-webrtc-sctp": "1.0.0",
|
|
55
|
+
"@agentdance/node-webrtc-stun": "1.0.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"typescript": "*",
|
|
59
|
+
"vitest": "*",
|
|
60
|
+
"@types/node": "*"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsc",
|
|
64
|
+
"test": "vitest run",
|
|
65
|
+
"typecheck": "tsc --noEmit",
|
|
66
|
+
"clean": "rm -rf dist"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { RTCDataChannelInit, RTCDataChannelState } from './types.js';
|
|
3
|
+
import type { SctpDataChannel } from '@agentdance/node-webrtc-sctp';
|
|
4
|
+
|
|
5
|
+
export declare interface RTCDataChannel {
|
|
6
|
+
on(event: 'open', listener: () => void): this;
|
|
7
|
+
on(event: 'message', listener: (data: Buffer | string | ArrayBuffer) => void): this;
|
|
8
|
+
on(event: 'close', listener: () => void): this;
|
|
9
|
+
on(event: 'closing', listener: () => void): this;
|
|
10
|
+
on(event: 'error', listener: (err: Error) => void): this;
|
|
11
|
+
on(event: 'bufferedamountlow', listener: () => void): this;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class RTCDataChannel extends EventEmitter {
|
|
15
|
+
readonly label: string;
|
|
16
|
+
readonly ordered: boolean;
|
|
17
|
+
readonly maxPacketLifeTime: number | null;
|
|
18
|
+
readonly maxRetransmits: number | null;
|
|
19
|
+
readonly protocol: string;
|
|
20
|
+
readonly negotiated: boolean;
|
|
21
|
+
id: number | null;
|
|
22
|
+
readyState: RTCDataChannelState = 'connecting';
|
|
23
|
+
binaryType: 'blob' | 'arraybuffer' = 'arraybuffer';
|
|
24
|
+
|
|
25
|
+
// bufferedAmount tracks bytes enqueued but not yet acknowledged
|
|
26
|
+
private _bufferedAmount = 0;
|
|
27
|
+
private _bufferedAmountLowThreshold = 0;
|
|
28
|
+
|
|
29
|
+
// Internal reference to the SCTP layer channel
|
|
30
|
+
_sctpChannel: SctpDataChannel | null;
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
label: string,
|
|
34
|
+
init: RTCDataChannelInit,
|
|
35
|
+
sctpChannel: SctpDataChannel | null,
|
|
36
|
+
) {
|
|
37
|
+
super();
|
|
38
|
+
this.label = label;
|
|
39
|
+
this.ordered = init.ordered ?? true;
|
|
40
|
+
this.maxPacketLifeTime = init.maxPacketLifeTime ?? null;
|
|
41
|
+
this.maxRetransmits = init.maxRetransmits ?? null;
|
|
42
|
+
this.protocol = init.protocol ?? '';
|
|
43
|
+
this.negotiated = init.negotiated ?? false;
|
|
44
|
+
this.id = init.id ?? null;
|
|
45
|
+
this._sctpChannel = sctpChannel;
|
|
46
|
+
|
|
47
|
+
if (sctpChannel) {
|
|
48
|
+
this._bindSctpChannel(sctpChannel);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get bufferedAmount(): number {
|
|
53
|
+
// Delegate to the underlying SCTP channel so that bufferedAmount tracks
|
|
54
|
+
// only bytes that have been enqueued but not yet acknowledged by the remote
|
|
55
|
+
// peer. This allows backpressure checks (channel.bufferedAmount >
|
|
56
|
+
// HIGH_WATERMARK) to work correctly.
|
|
57
|
+
if (this._sctpChannel) {
|
|
58
|
+
return this._sctpChannel.bufferedAmount;
|
|
59
|
+
}
|
|
60
|
+
return this._bufferedAmount;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get bufferedAmountLowThreshold(): number {
|
|
64
|
+
return this._bufferedAmountLowThreshold;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
set bufferedAmountLowThreshold(value: number) {
|
|
68
|
+
this._bufferedAmountLowThreshold = value;
|
|
69
|
+
// Propagate to underlying SCTP channel if bound
|
|
70
|
+
if (this._sctpChannel) {
|
|
71
|
+
this._sctpChannel.bufferedAmountLowThreshold = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_bindSctpChannel(channel: SctpDataChannel): void {
|
|
76
|
+
this._sctpChannel = channel;
|
|
77
|
+
this.id = channel.id;
|
|
78
|
+
|
|
79
|
+
// Sync threshold
|
|
80
|
+
channel.bufferedAmountLowThreshold = this._bufferedAmountLowThreshold;
|
|
81
|
+
|
|
82
|
+
channel.on('open', () => {
|
|
83
|
+
console.log(`[RTCDataChannel] SCTP 'open' for label="${this.label}" (streamId=${this.id})`);
|
|
84
|
+
this.readyState = 'open';
|
|
85
|
+
this.emit('open');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
channel.on('message', (data: Buffer | string) => {
|
|
89
|
+
if (data instanceof Buffer && this.binaryType === 'arraybuffer') {
|
|
90
|
+
// Expose as ArrayBuffer to match browser behaviour
|
|
91
|
+
const ab = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
92
|
+
this.emit('message', ab);
|
|
93
|
+
} else {
|
|
94
|
+
this.emit('message', data);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
channel.on('close', () => {
|
|
99
|
+
this.readyState = 'closed';
|
|
100
|
+
this._bufferedAmount = 0;
|
|
101
|
+
this.emit('close');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
channel.on('error', (err: Error) => {
|
|
105
|
+
this.emit('error', err);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Relay bufferedamountlow from SCTP layer
|
|
109
|
+
channel.on('bufferedamountlow', () => {
|
|
110
|
+
this.emit('bufferedamountlow');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// If already open (e.g. negotiated channel)
|
|
114
|
+
if (channel.state === 'open') {
|
|
115
|
+
this.readyState = 'open';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
send(data: string | Buffer | ArrayBuffer | ArrayBufferView): void {
|
|
120
|
+
if (this.readyState !== 'open') {
|
|
121
|
+
const err = new Error(`DataChannel "${this.label}" is not open (state: ${this.readyState})`);
|
|
122
|
+
(err as NodeJS.ErrnoException).code = 'InvalidStateError';
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
if (!this._sctpChannel) {
|
|
126
|
+
throw new Error(`DataChannel "${this.label}" has no underlying SCTP channel`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let buf: Buffer;
|
|
130
|
+
if (typeof data === 'string') {
|
|
131
|
+
// Track buffered bytes (UTF-8 encoded size)
|
|
132
|
+
buf = Buffer.from(data, 'utf8');
|
|
133
|
+
this._bufferedAmount += buf.byteLength;
|
|
134
|
+
this._sctpChannel.send(data);
|
|
135
|
+
} else if (data instanceof Buffer) {
|
|
136
|
+
buf = data;
|
|
137
|
+
this._bufferedAmount += buf.byteLength;
|
|
138
|
+
this._sctpChannel.send(buf);
|
|
139
|
+
} else if (data instanceof ArrayBuffer) {
|
|
140
|
+
buf = Buffer.from(data);
|
|
141
|
+
this._bufferedAmount += buf.byteLength;
|
|
142
|
+
this._sctpChannel.send(buf);
|
|
143
|
+
} else {
|
|
144
|
+
// ArrayBufferView
|
|
145
|
+
const view = data as ArrayBufferView;
|
|
146
|
+
buf = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
|
|
147
|
+
this._bufferedAmount += buf.byteLength;
|
|
148
|
+
this._sctpChannel.send(buf);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
close(): void {
|
|
153
|
+
if (this.readyState === 'closed' || this.readyState === 'closing') return;
|
|
154
|
+
this.readyState = 'closing';
|
|
155
|
+
this.emit('closing');
|
|
156
|
+
if (this._sctpChannel) {
|
|
157
|
+
this._sctpChannel.close();
|
|
158
|
+
// The 'close' event on _sctpChannel transitions us to 'closed'
|
|
159
|
+
} else {
|
|
160
|
+
// No underlying channel – transition immediately
|
|
161
|
+
this.readyState = 'closed';
|
|
162
|
+
this.emit('close');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export class RTCIceCandidate {
|
|
2
|
+
readonly candidate: string;
|
|
3
|
+
readonly sdpMid: string | null;
|
|
4
|
+
readonly sdpMLineIndex: number | null;
|
|
5
|
+
readonly usernameFragment: string | null;
|
|
6
|
+
|
|
7
|
+
// Parsed fields
|
|
8
|
+
readonly foundation: string | null = null;
|
|
9
|
+
readonly component: 'rtp' | 'rtcp' | null = null;
|
|
10
|
+
readonly protocol: 'udp' | 'tcp' | null = null;
|
|
11
|
+
readonly priority: number | null = null;
|
|
12
|
+
readonly address: string | null = null;
|
|
13
|
+
readonly port: number | null = null;
|
|
14
|
+
readonly type: 'host' | 'srflx' | 'relay' | 'prflx' | null = null;
|
|
15
|
+
relatedAddress: string | null = null;
|
|
16
|
+
relatedPort: number | null = null;
|
|
17
|
+
tcpType: string | null = null;
|
|
18
|
+
|
|
19
|
+
constructor(init: { candidate: string; sdpMid?: string; sdpMLineIndex?: number; usernameFragment?: string }) {
|
|
20
|
+
this.candidate = init.candidate ?? '';
|
|
21
|
+
this.sdpMid = init.sdpMid ?? null;
|
|
22
|
+
this.sdpMLineIndex = init.sdpMLineIndex ?? null;
|
|
23
|
+
this.usernameFragment = init.usernameFragment ?? null;
|
|
24
|
+
|
|
25
|
+
// Parse candidate string
|
|
26
|
+
const parsed = this._parse(this.candidate);
|
|
27
|
+
if (parsed) {
|
|
28
|
+
(this as { foundation: string | null }).foundation = parsed.foundation;
|
|
29
|
+
(this as { component: 'rtp' | 'rtcp' | null }).component = parsed.component;
|
|
30
|
+
(this as { protocol: 'udp' | 'tcp' | null }).protocol = parsed.protocol;
|
|
31
|
+
(this as { priority: number | null }).priority = parsed.priority;
|
|
32
|
+
(this as { address: string | null }).address = parsed.address;
|
|
33
|
+
(this as { port: number | null }).port = parsed.port;
|
|
34
|
+
(this as { type: 'host' | 'srflx' | 'relay' | 'prflx' | null }).type = parsed.type;
|
|
35
|
+
this.relatedAddress = parsed.relatedAddress;
|
|
36
|
+
this.relatedPort = parsed.relatedPort;
|
|
37
|
+
this.tcpType = parsed.tcpType;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private _parse(candidateStr: string): {
|
|
42
|
+
foundation: string | null;
|
|
43
|
+
component: 'rtp' | 'rtcp' | null;
|
|
44
|
+
protocol: 'udp' | 'tcp' | null;
|
|
45
|
+
priority: number | null;
|
|
46
|
+
address: string | null;
|
|
47
|
+
port: number | null;
|
|
48
|
+
type: 'host' | 'srflx' | 'relay' | 'prflx' | null;
|
|
49
|
+
relatedAddress: string | null;
|
|
50
|
+
relatedPort: number | null;
|
|
51
|
+
tcpType: string | null;
|
|
52
|
+
} | null {
|
|
53
|
+
// "candidate:foundation component protocol priority address port typ type ..."
|
|
54
|
+
const str = candidateStr.replace(/^candidate:/, '');
|
|
55
|
+
const parts = str.split(' ');
|
|
56
|
+
if (parts.length < 8) return null;
|
|
57
|
+
const [foundation, componentStr, protocol, priorityStr, address, portStr, , type, ...rest] = parts;
|
|
58
|
+
|
|
59
|
+
const result = {
|
|
60
|
+
foundation: foundation ?? null,
|
|
61
|
+
component: (componentStr === '1' ? 'rtp' : 'rtcp') as 'rtp' | 'rtcp' | null,
|
|
62
|
+
protocol: (protocol?.toLowerCase() as 'udp' | 'tcp') ?? null,
|
|
63
|
+
priority: priorityStr ? parseInt(priorityStr, 10) : null,
|
|
64
|
+
address: address ?? null,
|
|
65
|
+
port: portStr ? parseInt(portStr, 10) : null,
|
|
66
|
+
type: (type as 'host' | 'srflx' | 'relay' | 'prflx') ?? null,
|
|
67
|
+
relatedAddress: null as string | null,
|
|
68
|
+
relatedPort: null as number | null,
|
|
69
|
+
tcpType: null as string | null,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Parse extensions
|
|
73
|
+
for (let i = 0; i < rest.length - 1; i += 2) {
|
|
74
|
+
const key = rest[i];
|
|
75
|
+
const val = rest[i + 1];
|
|
76
|
+
if (key === 'raddr') result.relatedAddress = val ?? null;
|
|
77
|
+
else if (key === 'rport') result.relatedPort = val ? parseInt(val, 10) : null;
|
|
78
|
+
else if (key === 'tcptype') result.tcpType = val ?? null;
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
toJSON() {
|
|
84
|
+
return {
|
|
85
|
+
candidate: this.candidate,
|
|
86
|
+
sdpMid: this.sdpMid,
|
|
87
|
+
sdpMLineIndex: this.sdpMLineIndex,
|
|
88
|
+
usernameFragment: this.usernameFragment,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { RTCPeerConnection } from './peer-connection.js';
|
|
2
|
+
export { RTCSessionDescription } from './session-description.js';
|
|
3
|
+
export { RTCIceCandidate } from './ice-candidate.js';
|
|
4
|
+
export { RTCDataChannel } from './data-channel.js';
|
|
5
|
+
export { RTCRtpSender } from './rtp-sender.js';
|
|
6
|
+
export { RTCRtpReceiver } from './rtp-receiver.js';
|
|
7
|
+
export { RTCRtpTransceiver } from './rtp-transceiver.js';
|
|
8
|
+
export * from './types.js';
|