@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
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type {
|
|
3
|
+
RTCConfiguration,
|
|
4
|
+
RTCSessionDescriptionInit,
|
|
5
|
+
RTCSessionDescription,
|
|
6
|
+
RTCIceCandidateInit,
|
|
7
|
+
RTCOfferOptions,
|
|
8
|
+
RTCAnswerOptions,
|
|
9
|
+
RTCSignalingState,
|
|
10
|
+
RTCPeerConnectionState,
|
|
11
|
+
RTCIceConnectionState,
|
|
12
|
+
RTCIceGatheringState,
|
|
13
|
+
RTCDataChannelInit,
|
|
14
|
+
RTCRtpTransceiverDirection,
|
|
15
|
+
} from './types.js';
|
|
16
|
+
import { RTCDataChannel as _RTCDataChannel } from './data-channel.js';
|
|
17
|
+
import { RTCRtpTransceiver as _RTCRtpTransceiver } from './rtp-transceiver.js';
|
|
18
|
+
|
|
19
|
+
export { RTCSessionDescription } from './session-description.js';
|
|
20
|
+
export { RTCIceCandidate } from './ice-candidate.js';
|
|
21
|
+
export { RTCDataChannel } from './data-channel.js';
|
|
22
|
+
export { RTCRtpSender } from './rtp-sender.js';
|
|
23
|
+
export { RTCRtpReceiver } from './rtp-receiver.js';
|
|
24
|
+
export { RTCRtpTransceiver } from './rtp-transceiver.js';
|
|
25
|
+
|
|
26
|
+
export * from './types.js';
|
|
27
|
+
|
|
28
|
+
export declare interface RTCPeerConnection {
|
|
29
|
+
on(event: 'icecandidate', listener: (init: RTCIceCandidateInit | null) => void): this;
|
|
30
|
+
on(event: 'icecandidateerror', listener: (ev: { errorCode: number; errorText: string }) => void): this;
|
|
31
|
+
on(event: 'iceconnectionstatechange', listener: () => void): this;
|
|
32
|
+
on(event: 'icegatheringstatechange', listener: () => void): this;
|
|
33
|
+
on(event: 'connectionstatechange', listener: () => void): this;
|
|
34
|
+
on(event: 'signalingstatechange', listener: () => void): this;
|
|
35
|
+
on(event: 'negotiationneeded', listener: () => void): this;
|
|
36
|
+
on(event: 'datachannel', listener: (channel: import('./data-channel.js').RTCDataChannel) => void): this;
|
|
37
|
+
on(event: 'track', listener: (ev: RTCTrackEvent) => void): this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RTCTrackEvent {
|
|
41
|
+
receiver: import('./rtp-receiver.js').RTCRtpReceiver;
|
|
42
|
+
track: { kind: string };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class RTCPeerConnection extends EventEmitter {
|
|
46
|
+
readonly localDescription: RTCSessionDescription | null = null;
|
|
47
|
+
readonly remoteDescription: RTCSessionDescription | null = null;
|
|
48
|
+
readonly currentLocalDescription: RTCSessionDescription | null = null;
|
|
49
|
+
readonly currentRemoteDescription: RTCSessionDescription | null = null;
|
|
50
|
+
readonly pendingLocalDescription: RTCSessionDescription | null = null;
|
|
51
|
+
readonly pendingRemoteDescription: RTCSessionDescription | null = null;
|
|
52
|
+
|
|
53
|
+
signalingState: RTCSignalingState = 'stable';
|
|
54
|
+
connectionState: RTCPeerConnectionState = 'new';
|
|
55
|
+
iceConnectionState: RTCIceConnectionState = 'new';
|
|
56
|
+
iceGatheringState: RTCIceGatheringState = 'new';
|
|
57
|
+
|
|
58
|
+
private _config: Required<RTCConfiguration>;
|
|
59
|
+
private _isClosed = false;
|
|
60
|
+
private _transceivers: _RTCRtpTransceiver[] = [];
|
|
61
|
+
private _dataChannels: Map<string, _RTCDataChannel> = new Map();
|
|
62
|
+
private _pendingChannels: _RTCDataChannel[] = [];
|
|
63
|
+
|
|
64
|
+
// Protocol stack (initialized lazily)
|
|
65
|
+
private _internals: import('./internal/peer-internals.js').PeerInternals | undefined;
|
|
66
|
+
|
|
67
|
+
constructor(config: RTCConfiguration = {}) {
|
|
68
|
+
super();
|
|
69
|
+
this._config = {
|
|
70
|
+
iceServers: config.iceServers ?? [{ urls: 'stun:stun.l.google.com:19302' }],
|
|
71
|
+
iceTransportPolicy: config.iceTransportPolicy ?? 'all',
|
|
72
|
+
bundlePolicy: config.bundlePolicy ?? 'max-bundle',
|
|
73
|
+
rtcpMuxPolicy: config.rtcpMuxPolicy ?? 'require',
|
|
74
|
+
iceCandidatePoolSize: config.iceCandidatePoolSize ?? 0,
|
|
75
|
+
certificates: config.certificates ?? [],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async createOffer(options?: RTCOfferOptions): Promise<RTCSessionDescriptionInit> {
|
|
80
|
+
this._assertNotClosed();
|
|
81
|
+
const internals = await this._getOrCreateInternals();
|
|
82
|
+
return internals.createOffer(options ?? {});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async createAnswer(options?: RTCAnswerOptions): Promise<RTCSessionDescriptionInit> {
|
|
86
|
+
this._assertNotClosed();
|
|
87
|
+
const internals = await this._getOrCreateInternals();
|
|
88
|
+
return internals.createAnswer(options ?? {});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async setLocalDescription(description: RTCSessionDescriptionInit): Promise<void> {
|
|
92
|
+
this._assertNotClosed();
|
|
93
|
+
const internals = await this._getOrCreateInternals();
|
|
94
|
+
await internals.setLocalDescription(description);
|
|
95
|
+
(this as { localDescription: RTCSessionDescription | null }).localDescription = {
|
|
96
|
+
type: description.type,
|
|
97
|
+
sdp: description.sdp,
|
|
98
|
+
};
|
|
99
|
+
this._updateSignalingState(description.type, 'local');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async setRemoteDescription(description: RTCSessionDescriptionInit): Promise<void> {
|
|
103
|
+
this._assertNotClosed();
|
|
104
|
+
const internals = await this._getOrCreateInternals();
|
|
105
|
+
await internals.setRemoteDescription(description);
|
|
106
|
+
(this as { remoteDescription: RTCSessionDescription | null }).remoteDescription = {
|
|
107
|
+
type: description.type,
|
|
108
|
+
sdp: description.sdp,
|
|
109
|
+
};
|
|
110
|
+
this._updateSignalingState(description.type, 'remote');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async addIceCandidate(candidate: RTCIceCandidateInit | null): Promise<void> {
|
|
114
|
+
const internals = this._internals;
|
|
115
|
+
if (!internals) return;
|
|
116
|
+
// null or empty candidate string = end-of-candidates signal (RFC 8840 §4.4)
|
|
117
|
+
if (!candidate || !candidate.candidate) {
|
|
118
|
+
internals.iceAgent?.remoteGatheringComplete();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
await internals.addIceCandidate(candidate);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
createDataChannel(label: string, init?: RTCDataChannelInit): _RTCDataChannel {
|
|
125
|
+
this._assertNotClosed();
|
|
126
|
+
const channel = new _RTCDataChannel(label, init ?? {}, null);
|
|
127
|
+
// Use a unique key: label + id (if specified) + random suffix to avoid collisions
|
|
128
|
+
const key = label + ':' + (init?.id ?? '') + ':' + Math.random().toString(36).slice(2);
|
|
129
|
+
this._dataChannels.set(key, channel);
|
|
130
|
+
|
|
131
|
+
if (this._internals?.sctpAssociation?.state === 'connected') {
|
|
132
|
+
// SCTP is ready now — open immediately
|
|
133
|
+
this._internals.openDataChannel(channel);
|
|
134
|
+
} else if (this._internals) {
|
|
135
|
+
// Internals exist but SCTP not ready — hand off to internals pending queue
|
|
136
|
+
this._internals.openDataChannel(channel);
|
|
137
|
+
} else {
|
|
138
|
+
// Internals not yet created — store locally until SCTP is ready
|
|
139
|
+
this._pendingChannels.push(channel);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Clean up the map when the channel closes
|
|
143
|
+
channel.on('close', () => {
|
|
144
|
+
this._dataChannels.delete(key);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return channel;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
addTransceiver(
|
|
151
|
+
kind: 'audio' | 'video',
|
|
152
|
+
init?: { direction?: RTCRtpTransceiverDirection },
|
|
153
|
+
): _RTCRtpTransceiver {
|
|
154
|
+
this._assertNotClosed();
|
|
155
|
+
const transceiver = new _RTCRtpTransceiver(kind, init?.direction ?? 'sendrecv');
|
|
156
|
+
this._transceivers.push(transceiver);
|
|
157
|
+
this.emit('negotiationneeded');
|
|
158
|
+
return transceiver;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getTransceivers(): _RTCRtpTransceiver[] {
|
|
162
|
+
return [...this._transceivers];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getSenders(): import('./rtp-sender.js').RTCRtpSender[] {
|
|
166
|
+
return this._transceivers
|
|
167
|
+
.map((t) => t.sender)
|
|
168
|
+
.filter(Boolean) as import('./rtp-sender.js').RTCRtpSender[];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
getReceivers(): import('./rtp-receiver.js').RTCRtpReceiver[] {
|
|
172
|
+
return this._transceivers
|
|
173
|
+
.map((t) => t.receiver)
|
|
174
|
+
.filter(Boolean) as import('./rtp-receiver.js').RTCRtpReceiver[];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async getStats(): Promise<import('./stats.js').RTCStatsReport> {
|
|
178
|
+
const { RTCStatsReportImpl } = await import('./stats.js');
|
|
179
|
+
const internals = this._internals;
|
|
180
|
+
if (!internals) return new RTCStatsReportImpl(new Map());
|
|
181
|
+
return internals.getStats();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
close(): void {
|
|
185
|
+
if (this._isClosed) return;
|
|
186
|
+
this._isClosed = true;
|
|
187
|
+
this._internals?.close();
|
|
188
|
+
this._updateConnectionState('closed');
|
|
189
|
+
this.signalingState = 'closed';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
restartIce(): void {
|
|
193
|
+
this._internals?.restartIce();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private async _getOrCreateInternals(): Promise<import('./internal/peer-internals.js').PeerInternals> {
|
|
197
|
+
if (!this._internals) {
|
|
198
|
+
const { PeerInternals } = await import('./internal/peer-internals.js');
|
|
199
|
+
this._internals = new PeerInternals(this._config, this);
|
|
200
|
+
// Transfer any channels created before internals existed
|
|
201
|
+
for (const channel of this._pendingChannels) {
|
|
202
|
+
this._internals.openDataChannel(channel);
|
|
203
|
+
}
|
|
204
|
+
this._pendingChannels = [];
|
|
205
|
+
}
|
|
206
|
+
return this._internals;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private _assertNotClosed(): void {
|
|
210
|
+
if (this._isClosed) throw new Error('RTCPeerConnection is closed');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private _updateSignalingState(type: string, side: 'local' | 'remote'): void {
|
|
214
|
+
const prev = this.signalingState;
|
|
215
|
+
let next: RTCSignalingState = prev;
|
|
216
|
+
|
|
217
|
+
if (type === 'rollback') {
|
|
218
|
+
next = 'stable';
|
|
219
|
+
} else if (type === 'offer') {
|
|
220
|
+
next = side === 'local' ? 'have-local-offer' : 'have-remote-offer';
|
|
221
|
+
} else if (type === 'answer') {
|
|
222
|
+
next = 'stable';
|
|
223
|
+
} else if (type === 'pranswer') {
|
|
224
|
+
next = side === 'local' ? 'have-local-pranswer' : 'have-remote-pranswer';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (next !== prev) {
|
|
228
|
+
this.signalingState = next;
|
|
229
|
+
this.emit('signalingstatechange');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_updateConnectionState(state: RTCPeerConnectionState): void {
|
|
234
|
+
if (this.connectionState !== state) {
|
|
235
|
+
this.connectionState = state;
|
|
236
|
+
this.emit('connectionstatechange');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
_updateIceConnectionState(state: RTCIceConnectionState): void {
|
|
241
|
+
if (this.iceConnectionState !== state) {
|
|
242
|
+
this.iceConnectionState = state;
|
|
243
|
+
this.emit('iceconnectionstatechange');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_updateIceGatheringState(state: RTCIceGatheringState): void {
|
|
248
|
+
if (this.iceGatheringState !== state) {
|
|
249
|
+
this.iceGatheringState = state;
|
|
250
|
+
this.emit('icegatheringstatechange');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { RTCRtpReceiveParameters } from './types.js';
|
|
3
|
+
import type { MediaStreamTrack } from './rtp-sender.js';
|
|
4
|
+
|
|
5
|
+
export class RTCRtpReceiver extends EventEmitter {
|
|
6
|
+
readonly track: MediaStreamTrack | null = null;
|
|
7
|
+
readonly transport: RTCDtlsTransport | null = null;
|
|
8
|
+
|
|
9
|
+
getParameters(): RTCRtpReceiveParameters {
|
|
10
|
+
return { codecs: [], headerExtensions: [] };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async getStats(): Promise<RTCStatsReport> {
|
|
14
|
+
return new Map() as unknown as RTCStatsReport;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getContributingSources(): RTCRtpContributingSource[] {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getSynchronizationSources(): RTCRtpSynchronizationSource[] {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static getCapabilities(kind: string): { codecs: unknown[]; headerExtensions: unknown[] } | null {
|
|
26
|
+
if (kind === 'audio') {
|
|
27
|
+
return {
|
|
28
|
+
codecs: [
|
|
29
|
+
{ mimeType: 'audio/opus', clockRate: 48000, channels: 2 },
|
|
30
|
+
{ mimeType: 'audio/PCMU', clockRate: 8000 },
|
|
31
|
+
],
|
|
32
|
+
headerExtensions: [],
|
|
33
|
+
};
|
|
34
|
+
} else if (kind === 'video') {
|
|
35
|
+
return {
|
|
36
|
+
codecs: [
|
|
37
|
+
{ mimeType: 'video/VP8', clockRate: 90000 },
|
|
38
|
+
{ mimeType: 'video/VP9', clockRate: 90000 },
|
|
39
|
+
],
|
|
40
|
+
headerExtensions: [],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Stub types
|
|
48
|
+
interface RTCDtlsTransport {}
|
|
49
|
+
interface RTCStatsReport {}
|
|
50
|
+
interface RTCRtpContributingSource { source: number; timestamp: number; audioLevel?: number; }
|
|
51
|
+
interface RTCRtpSynchronizationSource extends RTCRtpContributingSource {}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { RTCRtpSendParameters, RTCRtpEncodingParameters } from './types.js';
|
|
3
|
+
|
|
4
|
+
// Minimal track interface for Node.js (browser MediaStreamTrack not available)
|
|
5
|
+
export interface MediaStreamTrack {
|
|
6
|
+
kind: string;
|
|
7
|
+
id: string;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
muted: boolean;
|
|
10
|
+
readyState: 'live' | 'ended';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class RTCRtpSender extends EventEmitter {
|
|
14
|
+
readonly track: MediaStreamTrack | null;
|
|
15
|
+
readonly transport: RTCDtlsTransport | null = null;
|
|
16
|
+
private _parameters: RTCRtpSendParameters;
|
|
17
|
+
|
|
18
|
+
constructor(track: MediaStreamTrack | null) {
|
|
19
|
+
super();
|
|
20
|
+
this.track = track;
|
|
21
|
+
this._parameters = {
|
|
22
|
+
codecs: [],
|
|
23
|
+
headerExtensions: [],
|
|
24
|
+
encodings: [],
|
|
25
|
+
transactionId: '',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getStats(): Promise<RTCStatsReport> {
|
|
30
|
+
return new Map() as unknown as RTCStatsReport;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getParameters(): RTCRtpSendParameters {
|
|
34
|
+
return { ...this._parameters };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async setParameters(params: RTCRtpSendParameters): Promise<void> {
|
|
38
|
+
this._parameters = params;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
replaceTrack(withTrack: MediaStreamTrack | null): Promise<void> {
|
|
42
|
+
(this as unknown as { track: MediaStreamTrack | null }).track = withTrack;
|
|
43
|
+
return Promise.resolve();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static getCapabilities(kind: string): { codecs: unknown[]; headerExtensions: unknown[] } | null {
|
|
47
|
+
// Return default codec capabilities
|
|
48
|
+
if (kind === 'audio') {
|
|
49
|
+
return {
|
|
50
|
+
codecs: [
|
|
51
|
+
{ mimeType: 'audio/opus', clockRate: 48000, channels: 2 },
|
|
52
|
+
{ mimeType: 'audio/PCMU', clockRate: 8000 },
|
|
53
|
+
{ mimeType: 'audio/PCMA', clockRate: 8000 },
|
|
54
|
+
],
|
|
55
|
+
headerExtensions: [
|
|
56
|
+
{ uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', id: 1 },
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
} else if (kind === 'video') {
|
|
60
|
+
return {
|
|
61
|
+
codecs: [
|
|
62
|
+
{ mimeType: 'video/VP8', clockRate: 90000 },
|
|
63
|
+
{ mimeType: 'video/VP9', clockRate: 90000 },
|
|
64
|
+
{ mimeType: 'video/H264', clockRate: 90000 },
|
|
65
|
+
],
|
|
66
|
+
headerExtensions: [
|
|
67
|
+
{ uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', id: 2 },
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Stub types for compatibility
|
|
76
|
+
interface RTCDtlsTransport {}
|
|
77
|
+
interface RTCStatsReport {}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { RTCRtpTransceiverDirection } from './types.js';
|
|
3
|
+
import { RTCRtpSender } from './rtp-sender.js';
|
|
4
|
+
import { RTCRtpReceiver } from './rtp-receiver.js';
|
|
5
|
+
|
|
6
|
+
export class RTCRtpTransceiver extends EventEmitter {
|
|
7
|
+
readonly kind: 'audio' | 'video';
|
|
8
|
+
readonly sender: RTCRtpSender;
|
|
9
|
+
readonly receiver: RTCRtpReceiver;
|
|
10
|
+
readonly mid: string | null = null;
|
|
11
|
+
direction: RTCRtpTransceiverDirection;
|
|
12
|
+
currentDirection: RTCRtpTransceiverDirection | null = null;
|
|
13
|
+
stopped = false;
|
|
14
|
+
|
|
15
|
+
constructor(kind: 'audio' | 'video', direction: RTCRtpTransceiverDirection) {
|
|
16
|
+
super();
|
|
17
|
+
this.kind = kind;
|
|
18
|
+
this.sender = new RTCRtpSender(null);
|
|
19
|
+
this.receiver = new RTCRtpReceiver();
|
|
20
|
+
this.direction = direction;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
stop(): void {
|
|
24
|
+
this.stopped = true;
|
|
25
|
+
this.direction = 'stopped';
|
|
26
|
+
this.currentDirection = 'stopped';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setCodecPreferences(codecs: RTCRtpCodecParameters[]): void {
|
|
30
|
+
// Store codec preferences for SDP generation
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface RTCRtpCodecParameters {
|
|
35
|
+
mimeType: string;
|
|
36
|
+
clockRate: number;
|
|
37
|
+
channels?: number;
|
|
38
|
+
sdpFmtpLine?: string;
|
|
39
|
+
payloadType: number;
|
|
40
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RTCSessionDescriptionInit, RTCSdpType } from './types.js';
|
|
2
|
+
|
|
3
|
+
export class RTCSessionDescription implements RTCSessionDescriptionInit {
|
|
4
|
+
readonly type: RTCSdpType;
|
|
5
|
+
readonly sdp: string;
|
|
6
|
+
|
|
7
|
+
constructor(init: RTCSessionDescriptionInit) {
|
|
8
|
+
this.type = init.type;
|
|
9
|
+
this.sdp = init.sdp;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
toJSON(): RTCSessionDescriptionInit {
|
|
13
|
+
return { type: this.type, sdp: this.sdp };
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/stats.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface RTCStats {
|
|
2
|
+
id: string;
|
|
3
|
+
type: string;
|
|
4
|
+
timestamp: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type RTCStatsReport = RTCStatsReportImpl;
|
|
8
|
+
|
|
9
|
+
export class RTCStatsReportImpl {
|
|
10
|
+
private readonly _map: Map<string, RTCStats>;
|
|
11
|
+
|
|
12
|
+
constructor(map: Map<string, RTCStats>) {
|
|
13
|
+
this._map = map;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
entries(): IterableIterator<[string, RTCStats]> {
|
|
17
|
+
return this._map.entries();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get(id: string): RTCStats | undefined {
|
|
21
|
+
return this._map.get(id);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
has(id: string): boolean {
|
|
25
|
+
return this._map.has(id);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
forEach(callbackfn: (value: RTCStats, key: string) => void): void {
|
|
29
|
+
this._map.forEach(callbackfn);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
[Symbol.iterator](): IterableIterator<[string, RTCStats]> {
|
|
33
|
+
return this._map.entries();
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
export type RTCSignalingState =
|
|
2
|
+
| 'stable'
|
|
3
|
+
| 'have-local-offer'
|
|
4
|
+
| 'have-remote-offer'
|
|
5
|
+
| 'have-local-pranswer'
|
|
6
|
+
| 'have-remote-pranswer'
|
|
7
|
+
| 'closed';
|
|
8
|
+
|
|
9
|
+
export type RTCPeerConnectionState =
|
|
10
|
+
| 'new'
|
|
11
|
+
| 'connecting'
|
|
12
|
+
| 'connected'
|
|
13
|
+
| 'disconnected'
|
|
14
|
+
| 'failed'
|
|
15
|
+
| 'closed';
|
|
16
|
+
|
|
17
|
+
export type RTCIceConnectionState =
|
|
18
|
+
| 'new'
|
|
19
|
+
| 'checking'
|
|
20
|
+
| 'connected'
|
|
21
|
+
| 'completed'
|
|
22
|
+
| 'failed'
|
|
23
|
+
| 'disconnected'
|
|
24
|
+
| 'closed';
|
|
25
|
+
|
|
26
|
+
export type RTCIceGatheringState = 'new' | 'gathering' | 'complete';
|
|
27
|
+
|
|
28
|
+
export type RTCSdpType = 'offer' | 'pranswer' | 'answer' | 'rollback';
|
|
29
|
+
|
|
30
|
+
export interface RTCSessionDescriptionInit {
|
|
31
|
+
type: RTCSdpType;
|
|
32
|
+
sdp: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RTCSessionDescription {
|
|
36
|
+
type: RTCSdpType;
|
|
37
|
+
sdp: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RTCIceCandidateInit {
|
|
41
|
+
candidate: string;
|
|
42
|
+
sdpMid?: string;
|
|
43
|
+
sdpMLineIndex?: number;
|
|
44
|
+
usernameFragment?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface RTCIceServer {
|
|
48
|
+
urls: string | string[];
|
|
49
|
+
username?: string;
|
|
50
|
+
credential?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RTCConfiguration {
|
|
54
|
+
iceServers?: RTCIceServer[];
|
|
55
|
+
iceTransportPolicy?: 'all' | 'relay';
|
|
56
|
+
bundlePolicy?: 'balanced' | 'max-bundle' | 'max-compat';
|
|
57
|
+
rtcpMuxPolicy?: 'require';
|
|
58
|
+
iceCandidatePoolSize?: number;
|
|
59
|
+
certificates?: RTCDtlsTransportCertificate[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface RTCDtlsTransportCertificate {
|
|
63
|
+
fingerprint: { algorithm: string; value: string };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RTCOfferOptions {
|
|
67
|
+
iceRestart?: boolean;
|
|
68
|
+
offerToReceiveAudio?: boolean;
|
|
69
|
+
offerToReceiveVideo?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface RTCAnswerOptions {}
|
|
73
|
+
|
|
74
|
+
export type RTCRtpTransceiverDirection =
|
|
75
|
+
| 'sendrecv'
|
|
76
|
+
| 'sendonly'
|
|
77
|
+
| 'recvonly'
|
|
78
|
+
| 'inactive'
|
|
79
|
+
| 'stopped';
|
|
80
|
+
|
|
81
|
+
export interface RTCRtpCodecParameters {
|
|
82
|
+
mimeType: string;
|
|
83
|
+
clockRate: number;
|
|
84
|
+
channels?: number;
|
|
85
|
+
sdpFmtpLine?: string;
|
|
86
|
+
payloadType: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface RTCRtpHeaderExtensionParameters {
|
|
90
|
+
uri: string;
|
|
91
|
+
id: number;
|
|
92
|
+
encrypted?: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface RTCRtpParameters {
|
|
96
|
+
codecs: RTCRtpCodecParameters[];
|
|
97
|
+
headerExtensions: RTCRtpHeaderExtensionParameters[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface RTCRtpSendParameters extends RTCRtpParameters {
|
|
101
|
+
encodings: RTCRtpEncodingParameters[];
|
|
102
|
+
transactionId: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface RTCRtpReceiveParameters extends RTCRtpParameters {}
|
|
106
|
+
|
|
107
|
+
export interface RTCRtpEncodingParameters {
|
|
108
|
+
rid?: string;
|
|
109
|
+
active?: boolean;
|
|
110
|
+
maxBitrate?: number;
|
|
111
|
+
scaleResolutionDownBy?: number;
|
|
112
|
+
ssrc?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface RTCSctpTransportInit {
|
|
116
|
+
maxMessageSize: number;
|
|
117
|
+
maxChannels: number;
|
|
118
|
+
state: 'new' | 'connecting' | 'connected' | 'closed';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export type RTCDataChannelState = 'connecting' | 'open' | 'closing' | 'closed';
|
|
122
|
+
|
|
123
|
+
export interface RTCDataChannelInit {
|
|
124
|
+
ordered?: boolean;
|
|
125
|
+
maxPacketLifeTime?: number;
|
|
126
|
+
maxRetransmits?: number;
|
|
127
|
+
protocol?: string;
|
|
128
|
+
negotiated?: boolean;
|
|
129
|
+
id?: number;
|
|
130
|
+
priority?: 'very-low' | 'low' | 'medium' | 'high';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface RTCStats {
|
|
134
|
+
id: string;
|
|
135
|
+
type: string;
|
|
136
|
+
timestamp: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface RTCIceCandidatePairStats extends RTCStats {
|
|
140
|
+
type: 'candidate-pair';
|
|
141
|
+
localCandidateId: string;
|
|
142
|
+
remoteCandidateId: string;
|
|
143
|
+
state: string;
|
|
144
|
+
nominated: boolean;
|
|
145
|
+
bytesSent: number;
|
|
146
|
+
bytesReceived: number;
|
|
147
|
+
totalRoundTripTime: number;
|
|
148
|
+
currentRoundTripTime?: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface RTCStatsReport {
|
|
152
|
+
entries(): IterableIterator<[string, RTCStats]>;
|
|
153
|
+
get(id: string): RTCStats | undefined;
|
|
154
|
+
has(id: string): boolean;
|
|
155
|
+
forEach(callbackfn: (value: RTCStats, key: string) => void): void;
|
|
156
|
+
}
|