@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.
- package/package.json +73 -70
- package/src/get-user-media.ts +0 -131
- package/src/gst-enum-maps.ts +0 -125
- package/src/gst-init.ts +0 -49
- package/src/gst-stats-parser.ts +0 -137
- package/src/gst-utils.ts +0 -41
- package/src/index.ts +0 -104
- package/src/internal/gst-types.ts +0 -122
- package/src/media-device-info.ts +0 -33
- package/src/media-devices.ts +0 -191
- package/src/media-stream-track.ts +0 -159
- package/src/media-stream.ts +0 -96
- package/src/register/data-channel.ts +0 -11
- package/src/register/error.ts +0 -11
- package/src/register/media-devices.ts +0 -10
- package/src/register/media.ts +0 -15
- package/src/register/peer-connection.ts +0 -20
- package/src/register.spec.ts +0 -55
- package/src/register.ts +0 -10
- package/src/rtc-certificate.ts +0 -110
- package/src/rtc-data-channel.ts +0 -283
- package/src/rtc-dtls-transport.ts +0 -48
- package/src/rtc-dtmf-sender.ts +0 -146
- package/src/rtc-error.ts +0 -49
- package/src/rtc-events.ts +0 -64
- package/src/rtc-ice-candidate.ts +0 -115
- package/src/rtc-ice-transport.ts +0 -104
- package/src/rtc-peer-connection.ts +0 -1039
- package/src/rtc-rtp-receiver.ts +0 -122
- package/src/rtc-rtp-sender.ts +0 -471
- package/src/rtc-rtp-transceiver.ts +0 -131
- package/src/rtc-sctp-transport.ts +0 -48
- package/src/rtc-session-description.ts +0 -64
- package/src/rtc-stats-report.ts +0 -39
- package/src/rtc-track-event.ts +0 -45
- package/src/rtp-capabilities.ts +0 -48
- package/src/tee-multiplexer.ts +0 -75
- package/src/test.mts +0 -11
- package/src/webrtc.spec.ts +0 -1186
- package/src/wpt-helpers.ts +0 -156
- package/src/wpt-media.spec.ts +0 -1154
- package/src/wpt.spec.ts +0 -1136
- package/tsconfig.json +0 -36
- package/tsconfig.tsbuildinfo +0 -1
package/src/wpt-media.spec.ts
DELETED
|
@@ -1,1154 +0,0 @@
|
|
|
1
|
-
// WPT-ported tests for @gjsify/webrtc Phase 2 (Media API surface).
|
|
2
|
-
//
|
|
3
|
-
// Ported from refs/wpt/webrtc/* (W3C, BSD-3-Clause). Tests cover transceiver,
|
|
4
|
-
// sender, receiver, codec capabilities, track events, and media stream classes.
|
|
5
|
-
// All tests use addTransceiver('audio'/'video') with synthetic transceivers —
|
|
6
|
-
// no getUserMedia or real media hardware required.
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect } from '@gjsify/unit';
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
RTCPeerConnection,
|
|
12
|
-
RTCRtpSender,
|
|
13
|
-
RTCRtpReceiver,
|
|
14
|
-
RTCRtpTransceiver,
|
|
15
|
-
RTCTrackEvent,
|
|
16
|
-
MediaStream,
|
|
17
|
-
MediaStreamTrack,
|
|
18
|
-
MediaStreamTrackEvent,
|
|
19
|
-
} from './index.js';
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
createPeerConnection,
|
|
23
|
-
exchangeOfferAnswer,
|
|
24
|
-
closePeerConnections,
|
|
25
|
-
} from './wpt-helpers.js';
|
|
26
|
-
|
|
27
|
-
// Helper: generate a remote answer for a given offer using a second peer
|
|
28
|
-
async function generateAnswer(offer: any): Promise<any> {
|
|
29
|
-
const pc = createPeerConnection();
|
|
30
|
-
await pc.setRemoteDescription(offer);
|
|
31
|
-
const answer = await pc.createAnswer();
|
|
32
|
-
await pc.setLocalDescription(answer);
|
|
33
|
-
closePeerConnections(pc);
|
|
34
|
-
return answer;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export default async () => {
|
|
38
|
-
|
|
39
|
-
// ==========================================================================
|
|
40
|
-
// RTCTrackEvent-constructor.html (7 tests)
|
|
41
|
-
// ==========================================================================
|
|
42
|
-
await describe('RTCTrackEvent constructor (WPT)', async () => {
|
|
43
|
-
await it('should construct with valid receiver, track, transceiver', async () => {
|
|
44
|
-
const pc = createPeerConnection();
|
|
45
|
-
const transceiver = pc.addTransceiver('audio');
|
|
46
|
-
const { receiver } = transceiver;
|
|
47
|
-
const { track } = receiver;
|
|
48
|
-
|
|
49
|
-
const ev = new RTCTrackEvent('track', { receiver, track, transceiver });
|
|
50
|
-
expect(ev.receiver).toBe(receiver);
|
|
51
|
-
expect(ev.track).toBe(track);
|
|
52
|
-
expect(ev.streams.length).toBe(0);
|
|
53
|
-
expect(ev.transceiver).toBe(transceiver);
|
|
54
|
-
expect(ev.type).toBe('track');
|
|
55
|
-
expect(ev.bubbles).toBe(false);
|
|
56
|
-
expect(ev.cancelable).toBe(false);
|
|
57
|
-
closePeerConnections(pc);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
await it('should construct with streams array', async () => {
|
|
61
|
-
const pc = createPeerConnection();
|
|
62
|
-
const transceiver = pc.addTransceiver('audio');
|
|
63
|
-
const { receiver } = transceiver;
|
|
64
|
-
const { track } = receiver;
|
|
65
|
-
const stream = new MediaStream([track]);
|
|
66
|
-
|
|
67
|
-
const ev = new RTCTrackEvent('track', {
|
|
68
|
-
receiver, track, transceiver, streams: [stream],
|
|
69
|
-
});
|
|
70
|
-
expect(ev.streams.length).toBe(1);
|
|
71
|
-
expect(ev.streams[0]).toBe(stream);
|
|
72
|
-
closePeerConnections(pc);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
await it('should construct with multiple streams', async () => {
|
|
76
|
-
const pc = createPeerConnection();
|
|
77
|
-
const transceiver = pc.addTransceiver('audio');
|
|
78
|
-
const { receiver } = transceiver;
|
|
79
|
-
const { track } = receiver;
|
|
80
|
-
const s1 = new MediaStream([track]);
|
|
81
|
-
const s2 = new MediaStream([track]);
|
|
82
|
-
|
|
83
|
-
const ev = new RTCTrackEvent('track', {
|
|
84
|
-
receiver, track, transceiver, streams: [s1, s2],
|
|
85
|
-
});
|
|
86
|
-
expect(ev.streams.length).toBe(2);
|
|
87
|
-
closePeerConnections(pc);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
await it('should construct with unrelated receiver, track, transceiver', async () => {
|
|
91
|
-
const pc = createPeerConnection();
|
|
92
|
-
const transceiver = pc.addTransceiver('audio');
|
|
93
|
-
const receiver = pc.addTransceiver('audio').receiver;
|
|
94
|
-
const track = pc.addTransceiver('audio').receiver.track;
|
|
95
|
-
const stream = new MediaStream();
|
|
96
|
-
|
|
97
|
-
const ev = new RTCTrackEvent('track', {
|
|
98
|
-
receiver, track, transceiver, streams: [stream],
|
|
99
|
-
});
|
|
100
|
-
expect(ev.receiver).toBe(receiver);
|
|
101
|
-
expect(ev.track).toBe(track);
|
|
102
|
-
expect(ev.transceiver).toBe(transceiver);
|
|
103
|
-
closePeerConnections(pc);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
await it('should throw TypeError when transceiver is missing', async () => {
|
|
107
|
-
const pc = createPeerConnection();
|
|
108
|
-
const transceiver = pc.addTransceiver('audio');
|
|
109
|
-
const { receiver } = transceiver;
|
|
110
|
-
const { track } = receiver;
|
|
111
|
-
|
|
112
|
-
expect(() => new RTCTrackEvent('track', { receiver, track } as any)).toThrow();
|
|
113
|
-
closePeerConnections(pc);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
await it('should throw TypeError when track is missing', async () => {
|
|
117
|
-
const pc = createPeerConnection();
|
|
118
|
-
const transceiver = pc.addTransceiver('audio');
|
|
119
|
-
const { receiver } = transceiver;
|
|
120
|
-
|
|
121
|
-
expect(() => new RTCTrackEvent('track', { receiver, transceiver } as any)).toThrow();
|
|
122
|
-
closePeerConnections(pc);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
await it('should throw TypeError when receiver is missing', async () => {
|
|
126
|
-
const pc = createPeerConnection();
|
|
127
|
-
const transceiver = pc.addTransceiver('audio');
|
|
128
|
-
const { track } = transceiver.receiver;
|
|
129
|
-
|
|
130
|
-
expect(() => new RTCTrackEvent('track', { track, transceiver } as any)).toThrow();
|
|
131
|
-
closePeerConnections(pc);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// ==========================================================================
|
|
136
|
-
// RTCPeerConnection-addTransceiver.https.html (12 of 14 tests)
|
|
137
|
-
// ==========================================================================
|
|
138
|
-
await describe('RTCPeerConnection.addTransceiver (WPT)', async () => {
|
|
139
|
-
await it('should throw TypeError for invalid kind', async () => {
|
|
140
|
-
const pc = createPeerConnection();
|
|
141
|
-
expect(() => pc.addTransceiver('invalid' as any)).toThrow();
|
|
142
|
-
closePeerConnections(pc);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
await it('should create audio transceiver with default sendrecv direction', async () => {
|
|
146
|
-
const pc = createPeerConnection();
|
|
147
|
-
const t = pc.addTransceiver('audio');
|
|
148
|
-
expect(t).toBeDefined();
|
|
149
|
-
expect(t.mid).toBeNull();
|
|
150
|
-
expect(t.stopped).toBe(false);
|
|
151
|
-
expect(t.direction).toBe('sendrecv');
|
|
152
|
-
expect(t.currentDirection).toBeNull();
|
|
153
|
-
expect(t.sender).toBeDefined();
|
|
154
|
-
expect(t.receiver).toBeDefined();
|
|
155
|
-
expect(t.receiver.track).toBeDefined();
|
|
156
|
-
expect(t.receiver.track.kind).toBe('audio');
|
|
157
|
-
closePeerConnections(pc);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
await it('should create video transceiver', async () => {
|
|
161
|
-
const pc = createPeerConnection();
|
|
162
|
-
const t = pc.addTransceiver('video');
|
|
163
|
-
expect(t.receiver.track.kind).toBe('video');
|
|
164
|
-
closePeerConnections(pc);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
await it('should add transceiver to getTransceivers list', async () => {
|
|
168
|
-
const pc = createPeerConnection();
|
|
169
|
-
const t = pc.addTransceiver('audio');
|
|
170
|
-
const transceivers = pc.getTransceivers();
|
|
171
|
-
expect(transceivers.length).toBe(1);
|
|
172
|
-
expect(transceivers[0]).toBe(t);
|
|
173
|
-
closePeerConnections(pc);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
await it('should add sender to getSenders list', async () => {
|
|
177
|
-
const pc = createPeerConnection();
|
|
178
|
-
const t = pc.addTransceiver('audio');
|
|
179
|
-
const senders = pc.getSenders();
|
|
180
|
-
expect(senders.length).toBe(1);
|
|
181
|
-
expect(senders[0]).toBe(t.sender);
|
|
182
|
-
closePeerConnections(pc);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
await it('should add receiver to getReceivers list', async () => {
|
|
186
|
-
const pc = createPeerConnection();
|
|
187
|
-
const t = pc.addTransceiver('audio');
|
|
188
|
-
const receivers = pc.getReceivers();
|
|
189
|
-
expect(receivers.length).toBe(1);
|
|
190
|
-
expect(receivers[0]).toBe(t.receiver);
|
|
191
|
-
closePeerConnections(pc);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
await it('should respect direction option sendonly', async () => {
|
|
195
|
-
const pc = createPeerConnection();
|
|
196
|
-
const t = pc.addTransceiver('audio', { direction: 'sendonly' });
|
|
197
|
-
expect(t.direction).toBe('sendonly');
|
|
198
|
-
closePeerConnections(pc);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
await it('should respect direction option recvonly', async () => {
|
|
202
|
-
const pc = createPeerConnection();
|
|
203
|
-
const t = pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
204
|
-
expect(t.direction).toBe('recvonly');
|
|
205
|
-
closePeerConnections(pc);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
await it('should respect direction option inactive', async () => {
|
|
209
|
-
const pc = createPeerConnection();
|
|
210
|
-
const t = pc.addTransceiver('audio', { direction: 'inactive' });
|
|
211
|
-
expect(t.direction).toBe('inactive');
|
|
212
|
-
closePeerConnections(pc);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
await it('should throw TypeError for invalid direction', async () => {
|
|
216
|
-
const pc = createPeerConnection();
|
|
217
|
-
expect(() => pc.addTransceiver('audio', { direction: 'invalid' as any })).toThrow();
|
|
218
|
-
closePeerConnections(pc);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
await it('should support multiple transceivers', async () => {
|
|
222
|
-
const pc = createPeerConnection();
|
|
223
|
-
pc.addTransceiver('audio');
|
|
224
|
-
pc.addTransceiver('video');
|
|
225
|
-
expect(pc.getTransceivers().length).toBe(2);
|
|
226
|
-
expect(pc.getSenders().length).toBe(2);
|
|
227
|
-
expect(pc.getReceivers().length).toBe(2);
|
|
228
|
-
closePeerConnections(pc);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
await it('should throw InvalidStateError after close', async () => {
|
|
232
|
-
const pc = createPeerConnection();
|
|
233
|
-
pc.close();
|
|
234
|
-
expect(() => pc.addTransceiver('audio')).toThrow();
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// ==========================================================================
|
|
239
|
-
// RTCPeerConnection-getTransceivers.html (1 test)
|
|
240
|
-
// ==========================================================================
|
|
241
|
-
await describe('RTCPeerConnection.getTransceivers initial state (WPT)', async () => {
|
|
242
|
-
await it('should return empty arrays initially', async () => {
|
|
243
|
-
const pc = createPeerConnection();
|
|
244
|
-
expect(pc.getSenders().length).toBe(0);
|
|
245
|
-
expect(pc.getReceivers().length).toBe(0);
|
|
246
|
-
expect(pc.getTransceivers().length).toBe(0);
|
|
247
|
-
closePeerConnections(pc);
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
// ==========================================================================
|
|
252
|
-
// RTCRtpTransceiver-direction.html (3 tests)
|
|
253
|
-
// ==========================================================================
|
|
254
|
-
await describe('RTCRtpTransceiver.direction (WPT)', async () => {
|
|
255
|
-
await it('setting direction should change transceiver.direction', async () => {
|
|
256
|
-
const pc = createPeerConnection();
|
|
257
|
-
const t = pc.addTransceiver('audio');
|
|
258
|
-
expect(t.direction).toBe('sendrecv');
|
|
259
|
-
expect(t.currentDirection).toBeNull();
|
|
260
|
-
|
|
261
|
-
t.direction = 'recvonly';
|
|
262
|
-
expect(t.direction).toBe('recvonly');
|
|
263
|
-
expect(t.currentDirection).toBeNull();
|
|
264
|
-
closePeerConnections(pc);
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
await it('setting same direction should have no effect', async () => {
|
|
268
|
-
const pc = createPeerConnection();
|
|
269
|
-
const t = pc.addTransceiver('audio', { direction: 'sendonly' });
|
|
270
|
-
expect(t.direction).toBe('sendonly');
|
|
271
|
-
t.direction = 'sendonly';
|
|
272
|
-
expect(t.direction).toBe('sendonly');
|
|
273
|
-
closePeerConnections(pc);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
await it('setting direction independent of currentDirection', async () => {
|
|
277
|
-
const pc = createPeerConnection();
|
|
278
|
-
const t = pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
279
|
-
expect(t.direction).toBe('recvonly');
|
|
280
|
-
|
|
281
|
-
const offer = await pc.createOffer();
|
|
282
|
-
await pc.setLocalDescription(offer);
|
|
283
|
-
const answer = await generateAnswer(offer);
|
|
284
|
-
await pc.setRemoteDescription(answer);
|
|
285
|
-
|
|
286
|
-
t.direction = 'sendrecv';
|
|
287
|
-
expect(t.direction).toBe('sendrecv');
|
|
288
|
-
closePeerConnections(pc);
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
// ==========================================================================
|
|
293
|
-
// RTCRtpTransceiver-stop.html (selected tests)
|
|
294
|
-
// ==========================================================================
|
|
295
|
-
await describe('RTCRtpTransceiver.stop (WPT)', async () => {
|
|
296
|
-
await it('stop should set stopped to true', async () => {
|
|
297
|
-
const pc = createPeerConnection();
|
|
298
|
-
const t = pc.addTransceiver('audio');
|
|
299
|
-
expect(t.stopped).toBe(false);
|
|
300
|
-
t.stop();
|
|
301
|
-
expect(t.stopped).toBe(true);
|
|
302
|
-
closePeerConnections(pc);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
await it('stop should make direction return stopped', async () => {
|
|
306
|
-
const pc = createPeerConnection();
|
|
307
|
-
const t = pc.addTransceiver('audio');
|
|
308
|
-
t.stop();
|
|
309
|
-
expect(t.direction).toBe('stopped');
|
|
310
|
-
closePeerConnections(pc);
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
await it('setting direction on stopped transceiver should throw', async () => {
|
|
314
|
-
const pc = createPeerConnection();
|
|
315
|
-
const t = pc.addTransceiver('audio');
|
|
316
|
-
t.stop();
|
|
317
|
-
expect(() => { t.direction = 'sendrecv'; }).toThrow();
|
|
318
|
-
closePeerConnections(pc);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
await it('stop should make mid return null', async () => {
|
|
322
|
-
const pc = createPeerConnection();
|
|
323
|
-
const t = pc.addTransceiver('audio');
|
|
324
|
-
t.stop();
|
|
325
|
-
expect(t.mid).toBeNull();
|
|
326
|
-
closePeerConnections(pc);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
await it('stop should make currentDirection return null', async () => {
|
|
330
|
-
const pc = createPeerConnection();
|
|
331
|
-
const t = pc.addTransceiver('audio');
|
|
332
|
-
t.stop();
|
|
333
|
-
expect(t.currentDirection).toBeNull();
|
|
334
|
-
closePeerConnections(pc);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
await it('stopping twice should be idempotent', async () => {
|
|
338
|
-
const pc = createPeerConnection();
|
|
339
|
-
const t = pc.addTransceiver('audio');
|
|
340
|
-
t.stop();
|
|
341
|
-
t.stop();
|
|
342
|
-
expect(t.stopped).toBe(true);
|
|
343
|
-
closePeerConnections(pc);
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
// ==========================================================================
|
|
348
|
-
// RTCRtpSender-getCapabilities.html (3 tests)
|
|
349
|
-
// ==========================================================================
|
|
350
|
-
await describe('RTCRtpSender.getCapabilities (WPT)', async () => {
|
|
351
|
-
await it('should return capabilities for audio', async () => {
|
|
352
|
-
const caps = RTCRtpSender.getCapabilities('audio');
|
|
353
|
-
expect(caps).toBeDefined();
|
|
354
|
-
expect(caps).not.toBeNull();
|
|
355
|
-
expect(Array.isArray(caps!.codecs)).toBe(true);
|
|
356
|
-
expect(caps!.codecs.length).toBeGreaterThan(0);
|
|
357
|
-
expect(Array.isArray(caps!.headerExtensions)).toBe(true);
|
|
358
|
-
for (const codec of caps!.codecs) {
|
|
359
|
-
expect(typeof codec.mimeType).toBe('string');
|
|
360
|
-
expect(typeof codec.clockRate).toBe('number');
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
await it('should return capabilities for video', async () => {
|
|
365
|
-
const caps = RTCRtpSender.getCapabilities('video');
|
|
366
|
-
expect(caps).toBeDefined();
|
|
367
|
-
expect(caps).not.toBeNull();
|
|
368
|
-
expect(caps!.codecs.length).toBeGreaterThan(0);
|
|
369
|
-
for (const codec of caps!.codecs) {
|
|
370
|
-
expect(typeof codec.mimeType).toBe('string');
|
|
371
|
-
expect(typeof codec.clockRate).toBe('number');
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
await it('should return null for unknown kind', async () => {
|
|
376
|
-
const caps = RTCRtpSender.getCapabilities('dummy');
|
|
377
|
-
expect(caps).toBeNull();
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
// ==========================================================================
|
|
382
|
-
// RTCRtpReceiver-getCapabilities.html (3 tests)
|
|
383
|
-
// ==========================================================================
|
|
384
|
-
await describe('RTCRtpReceiver.getCapabilities (WPT)', async () => {
|
|
385
|
-
await it('should return capabilities for audio', async () => {
|
|
386
|
-
const caps = RTCRtpReceiver.getCapabilities('audio');
|
|
387
|
-
expect(caps).not.toBeNull();
|
|
388
|
-
expect(caps!.codecs.length).toBeGreaterThan(0);
|
|
389
|
-
expect(caps!.headerExtensions.length).toBeGreaterThan(0);
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
await it('should return capabilities for video', async () => {
|
|
393
|
-
const caps = RTCRtpReceiver.getCapabilities('video');
|
|
394
|
-
expect(caps).not.toBeNull();
|
|
395
|
-
expect(caps!.codecs.length).toBeGreaterThan(0);
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
await it('should return null for unknown kind', async () => {
|
|
399
|
-
expect(RTCRtpReceiver.getCapabilities('dummy')).toBeNull();
|
|
400
|
-
});
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
// ==========================================================================
|
|
404
|
-
// RTCRtpSender-setParameters.html (3 tests)
|
|
405
|
-
// ==========================================================================
|
|
406
|
-
await describe('RTCRtpSender.setParameters (WPT)', async () => {
|
|
407
|
-
await it('getParameters should return valid structure', async () => {
|
|
408
|
-
const pc = createPeerConnection();
|
|
409
|
-
const t = pc.addTransceiver('audio');
|
|
410
|
-
const params = t.sender.getParameters();
|
|
411
|
-
expect(params).toBeDefined();
|
|
412
|
-
expect(typeof params.transactionId).toBe('string');
|
|
413
|
-
expect(Array.isArray(params.encodings)).toBe(true);
|
|
414
|
-
expect(Array.isArray(params.codecs)).toBe(true);
|
|
415
|
-
expect(Array.isArray(params.headerExtensions)).toBe(true);
|
|
416
|
-
expect(typeof params.rtcp).toBe('object');
|
|
417
|
-
closePeerConnections(pc);
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
await it('setParameters should resolve with same params', async () => {
|
|
421
|
-
const pc = createPeerConnection();
|
|
422
|
-
const t = pc.addTransceiver('audio');
|
|
423
|
-
const params = t.sender.getParameters();
|
|
424
|
-
await t.sender.setParameters(params);
|
|
425
|
-
closePeerConnections(pc);
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
await it('setParameters should reject with mismatched transactionId', async () => {
|
|
429
|
-
const pc = createPeerConnection();
|
|
430
|
-
const t = pc.addTransceiver('audio');
|
|
431
|
-
const params = t.sender.getParameters();
|
|
432
|
-
params.transactionId = 'wrong-id';
|
|
433
|
-
let threw = false;
|
|
434
|
-
try {
|
|
435
|
-
await t.sender.setParameters(params);
|
|
436
|
-
} catch (e: any) {
|
|
437
|
-
threw = true;
|
|
438
|
-
expect(e.name).toBe('InvalidModificationError');
|
|
439
|
-
}
|
|
440
|
-
expect(threw).toBe(true);
|
|
441
|
-
closePeerConnections(pc);
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
// ==========================================================================
|
|
446
|
-
// RTCRtpReceiver-jitterBufferTarget.html (14 tests)
|
|
447
|
-
// ==========================================================================
|
|
448
|
-
await describe('RTCRtpReceiver.jitterBufferTarget (WPT)', async () => {
|
|
449
|
-
await it('default should be null for audio', async () => {
|
|
450
|
-
const pc = createPeerConnection();
|
|
451
|
-
const t = pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
452
|
-
expect(t.receiver.jitterBufferTarget).toBeNull();
|
|
453
|
-
closePeerConnections(pc);
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
await it('default should be null for video', async () => {
|
|
457
|
-
const pc = createPeerConnection();
|
|
458
|
-
const t = pc.addTransceiver('video', { direction: 'recvonly' });
|
|
459
|
-
expect(t.receiver.jitterBufferTarget).toBeNull();
|
|
460
|
-
closePeerConnections(pc);
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
await it('should accept positive value', async () => {
|
|
464
|
-
const pc = createPeerConnection();
|
|
465
|
-
const t = pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
466
|
-
t.receiver.jitterBufferTarget = 500;
|
|
467
|
-
expect(t.receiver.jitterBufferTarget).toBe(500);
|
|
468
|
-
closePeerConnections(pc);
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
await it('should accept 0', async () => {
|
|
472
|
-
const pc = createPeerConnection();
|
|
473
|
-
const t = pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
474
|
-
t.receiver.jitterBufferTarget = 0;
|
|
475
|
-
expect(t.receiver.jitterBufferTarget).toBe(0);
|
|
476
|
-
closePeerConnections(pc);
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
await it('should accept 4000 (max)', async () => {
|
|
480
|
-
const pc = createPeerConnection();
|
|
481
|
-
const t = pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
482
|
-
t.receiver.jitterBufferTarget = 4000;
|
|
483
|
-
expect(t.receiver.jitterBufferTarget).toBe(4000);
|
|
484
|
-
closePeerConnections(pc);
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
await it('should throw RangeError for > 4000', async () => {
|
|
488
|
-
const pc = createPeerConnection();
|
|
489
|
-
const t = pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
490
|
-
expect(() => { t.receiver.jitterBufferTarget = 4001; }).toThrow();
|
|
491
|
-
closePeerConnections(pc);
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
await it('should throw RangeError for negative value', async () => {
|
|
495
|
-
const pc = createPeerConnection();
|
|
496
|
-
const t = pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
497
|
-
expect(() => { t.receiver.jitterBufferTarget = -1; }).toThrow();
|
|
498
|
-
closePeerConnections(pc);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
await it('should retain old value on error', async () => {
|
|
502
|
-
const pc = createPeerConnection();
|
|
503
|
-
const t = pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
504
|
-
t.receiver.jitterBufferTarget = 200;
|
|
505
|
-
try { t.receiver.jitterBufferTarget = -1; } catch {}
|
|
506
|
-
expect(t.receiver.jitterBufferTarget).toBe(200);
|
|
507
|
-
closePeerConnections(pc);
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
await it('should accept reset to null', async () => {
|
|
511
|
-
const pc = createPeerConnection();
|
|
512
|
-
const t = pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
513
|
-
t.receiver.jitterBufferTarget = 500;
|
|
514
|
-
t.receiver.jitterBufferTarget = null;
|
|
515
|
-
expect(t.receiver.jitterBufferTarget).toBeNull();
|
|
516
|
-
closePeerConnections(pc);
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
await it('should accept value for video receiver', async () => {
|
|
520
|
-
const pc = createPeerConnection();
|
|
521
|
-
const t = pc.addTransceiver('video', { direction: 'recvonly' });
|
|
522
|
-
t.receiver.jitterBufferTarget = 1000;
|
|
523
|
-
expect(t.receiver.jitterBufferTarget).toBe(1000);
|
|
524
|
-
closePeerConnections(pc);
|
|
525
|
-
});
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
// ==========================================================================
|
|
529
|
-
// RTCRtpTransceiver-setCodecPreferences.html (selected tests)
|
|
530
|
-
// ==========================================================================
|
|
531
|
-
await describe('RTCRtpTransceiver.setCodecPreferences (WPT)', async () => {
|
|
532
|
-
await it('should accept empty array (reset)', async () => {
|
|
533
|
-
const pc = createPeerConnection();
|
|
534
|
-
const t = pc.addTransceiver('audio');
|
|
535
|
-
t.setCodecPreferences([]);
|
|
536
|
-
closePeerConnections(pc);
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
await it('should accept valid audio codecs from getCapabilities', async () => {
|
|
540
|
-
const pc = createPeerConnection();
|
|
541
|
-
const t = pc.addTransceiver('audio');
|
|
542
|
-
const caps = RTCRtpReceiver.getCapabilities('audio');
|
|
543
|
-
t.setCodecPreferences(caps!.codecs);
|
|
544
|
-
closePeerConnections(pc);
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
await it('should accept valid video codecs from getCapabilities', async () => {
|
|
548
|
-
const pc = createPeerConnection();
|
|
549
|
-
const t = pc.addTransceiver('video');
|
|
550
|
-
const caps = RTCRtpReceiver.getCapabilities('video');
|
|
551
|
-
t.setCodecPreferences(caps!.codecs);
|
|
552
|
-
closePeerConnections(pc);
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
await it('should accept reordered codecs', async () => {
|
|
556
|
-
const pc = createPeerConnection();
|
|
557
|
-
const t = pc.addTransceiver('audio');
|
|
558
|
-
const caps = RTCRtpReceiver.getCapabilities('audio');
|
|
559
|
-
const reversed = [...caps!.codecs].reverse();
|
|
560
|
-
t.setCodecPreferences(reversed);
|
|
561
|
-
closePeerConnections(pc);
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
await it('should throw for codec not in capabilities', async () => {
|
|
565
|
-
const pc = createPeerConnection();
|
|
566
|
-
const t = pc.addTransceiver('audio');
|
|
567
|
-
expect(() => {
|
|
568
|
-
t.setCodecPreferences([{
|
|
569
|
-
mimeType: 'audio/nonexistent',
|
|
570
|
-
clockRate: 48000,
|
|
571
|
-
}]);
|
|
572
|
-
}).toThrow();
|
|
573
|
-
closePeerConnections(pc);
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
await it('should throw for modified clockRate', async () => {
|
|
577
|
-
const pc = createPeerConnection();
|
|
578
|
-
const t = pc.addTransceiver('audio');
|
|
579
|
-
const caps = RTCRtpReceiver.getCapabilities('audio');
|
|
580
|
-
const modified = [{ ...caps!.codecs[0], clockRate: 12345 }];
|
|
581
|
-
expect(() => t.setCodecPreferences(modified)).toThrow();
|
|
582
|
-
closePeerConnections(pc);
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
await it('should accept subset of codecs', async () => {
|
|
586
|
-
const pc = createPeerConnection();
|
|
587
|
-
const t = pc.addTransceiver('audio');
|
|
588
|
-
const caps = RTCRtpReceiver.getCapabilities('audio');
|
|
589
|
-
const subset = caps!.codecs.filter(c => c.mimeType.toLowerCase() === 'audio/opus');
|
|
590
|
-
if (subset.length > 0) {
|
|
591
|
-
t.setCodecPreferences(subset);
|
|
592
|
-
}
|
|
593
|
-
closePeerConnections(pc);
|
|
594
|
-
});
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
// ==========================================================================
|
|
598
|
-
// MediaStream (basic tests)
|
|
599
|
-
// ==========================================================================
|
|
600
|
-
await describe('MediaStream', async () => {
|
|
601
|
-
await it('should create empty stream', async () => {
|
|
602
|
-
const s = new MediaStream();
|
|
603
|
-
expect(s.id).toBeDefined();
|
|
604
|
-
expect(typeof s.id).toBe('string');
|
|
605
|
-
expect(s.active).toBe(false);
|
|
606
|
-
expect(s.getTracks().length).toBe(0);
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
await it('should create stream from tracks array', async () => {
|
|
610
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
611
|
-
const s = new MediaStream([track]);
|
|
612
|
-
expect(s.getTracks().length).toBe(1);
|
|
613
|
-
expect(s.getAudioTracks().length).toBe(1);
|
|
614
|
-
expect(s.getVideoTracks().length).toBe(0);
|
|
615
|
-
expect(s.active).toBe(true);
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
await it('should clone stream', async () => {
|
|
619
|
-
const track = new MediaStreamTrack({ kind: 'video' });
|
|
620
|
-
const s1 = new MediaStream([track]);
|
|
621
|
-
const s2 = s1.clone();
|
|
622
|
-
expect(s2.id).not.toBe(s1.id);
|
|
623
|
-
expect(s2.getTracks().length).toBe(1);
|
|
624
|
-
expect(s2.getTracks()[0].id).not.toBe(track.id);
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
await it('should getTrackById', async () => {
|
|
628
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
629
|
-
const s = new MediaStream([track]);
|
|
630
|
-
expect(s.getTrackById(track.id)).toBe(track);
|
|
631
|
-
expect(s.getTrackById('nonexistent')).toBeNull();
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
await it('should addTrack and fire event', async () => {
|
|
635
|
-
const s = new MediaStream();
|
|
636
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
637
|
-
let fired = false;
|
|
638
|
-
s.addEventListener('addtrack', (ev: Event) => {
|
|
639
|
-
fired = true;
|
|
640
|
-
expect((ev as MediaStreamTrackEvent).track).toBe(track);
|
|
641
|
-
});
|
|
642
|
-
s.addTrack(track);
|
|
643
|
-
expect(s.getTracks().length).toBe(1);
|
|
644
|
-
expect(fired).toBe(true);
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
await it('should removeTrack and fire event', async () => {
|
|
648
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
649
|
-
const s = new MediaStream([track]);
|
|
650
|
-
let fired = false;
|
|
651
|
-
s.addEventListener('removetrack', () => { fired = true; });
|
|
652
|
-
s.removeTrack(track);
|
|
653
|
-
expect(s.getTracks().length).toBe(0);
|
|
654
|
-
expect(fired).toBe(true);
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
await it('should not add duplicate track', async () => {
|
|
658
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
659
|
-
const s = new MediaStream([track]);
|
|
660
|
-
s.addTrack(track);
|
|
661
|
-
expect(s.getTracks().length).toBe(1);
|
|
662
|
-
});
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
// ==========================================================================
|
|
666
|
-
// MediaStreamTrack (basic tests)
|
|
667
|
-
// ==========================================================================
|
|
668
|
-
await describe('MediaStreamTrack', async () => {
|
|
669
|
-
await it('should have correct initial properties', async () => {
|
|
670
|
-
const t = new MediaStreamTrack({ kind: 'audio', label: 'mic' });
|
|
671
|
-
expect(t.kind).toBe('audio');
|
|
672
|
-
expect(t.label).toBe('mic');
|
|
673
|
-
expect(t.readyState).toBe('live');
|
|
674
|
-
expect(t.enabled).toBe(true);
|
|
675
|
-
expect(t.muted).toBe(false);
|
|
676
|
-
expect(typeof t.id).toBe('string');
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
await it('should clone with new id', async () => {
|
|
680
|
-
const t = new MediaStreamTrack({ kind: 'video' });
|
|
681
|
-
const c = t.clone();
|
|
682
|
-
expect(c.kind).toBe('video');
|
|
683
|
-
expect(c.id).not.toBe(t.id);
|
|
684
|
-
expect(c.readyState).toBe('live');
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
await it('stop should set readyState to ended', async () => {
|
|
688
|
-
const t = new MediaStreamTrack({ kind: 'audio' });
|
|
689
|
-
t.stop();
|
|
690
|
-
expect(t.readyState).toBe('ended');
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
await it('should toggle enabled', async () => {
|
|
694
|
-
const t = new MediaStreamTrack({ kind: 'audio' });
|
|
695
|
-
t.enabled = false;
|
|
696
|
-
expect(t.enabled).toBe(false);
|
|
697
|
-
t.enabled = true;
|
|
698
|
-
expect(t.enabled).toBe(true);
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
await it('should validate contentHint for audio', async () => {
|
|
702
|
-
const t = new MediaStreamTrack({ kind: 'audio' });
|
|
703
|
-
t.contentHint = 'speech';
|
|
704
|
-
expect(t.contentHint).toBe('speech');
|
|
705
|
-
t.contentHint = 'invalid';
|
|
706
|
-
expect(t.contentHint).toBe('speech');
|
|
707
|
-
t.contentHint = '';
|
|
708
|
-
expect(t.contentHint).toBe('');
|
|
709
|
-
});
|
|
710
|
-
|
|
711
|
-
await it('should validate contentHint for video', async () => {
|
|
712
|
-
const t = new MediaStreamTrack({ kind: 'video' });
|
|
713
|
-
t.contentHint = 'motion';
|
|
714
|
-
expect(t.contentHint).toBe('motion');
|
|
715
|
-
t.contentHint = 'speech';
|
|
716
|
-
expect(t.contentHint).toBe('motion');
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
await it('getCapabilities should return empty object', async () => {
|
|
720
|
-
const t = new MediaStreamTrack({ kind: 'audio' });
|
|
721
|
-
const caps = t.getCapabilities();
|
|
722
|
-
expect(typeof caps).toBe('object');
|
|
723
|
-
});
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
// ==========================================================================
|
|
727
|
-
// RTCRtpReceiver.getParameters (basic test)
|
|
728
|
-
// ==========================================================================
|
|
729
|
-
await describe('RTCRtpReceiver.getParameters (WPT)', async () => {
|
|
730
|
-
await it('should return valid parameter structure', async () => {
|
|
731
|
-
const pc = createPeerConnection();
|
|
732
|
-
const t = pc.addTransceiver('audio');
|
|
733
|
-
const params = t.receiver.getParameters();
|
|
734
|
-
expect(params).toBeDefined();
|
|
735
|
-
expect(Array.isArray(params.codecs)).toBe(true);
|
|
736
|
-
expect(Array.isArray(params.headerExtensions)).toBe(true);
|
|
737
|
-
expect(typeof params.rtcp).toBe('object');
|
|
738
|
-
closePeerConnections(pc);
|
|
739
|
-
});
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
// ==========================================================================
|
|
743
|
-
// Receiver/Sender track properties
|
|
744
|
-
// ==========================================================================
|
|
745
|
-
await describe('Receiver and Sender track properties', async () => {
|
|
746
|
-
await it('receiver track should be audio for audio transceiver', async () => {
|
|
747
|
-
const pc = createPeerConnection();
|
|
748
|
-
const t = pc.addTransceiver('audio');
|
|
749
|
-
expect(t.receiver.track.kind).toBe('audio');
|
|
750
|
-
expect(t.receiver.track.readyState).toBe('live');
|
|
751
|
-
expect(t.receiver.track.muted).toBe(true);
|
|
752
|
-
closePeerConnections(pc);
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
await it('receiver track should be video for video transceiver', async () => {
|
|
756
|
-
const pc = createPeerConnection();
|
|
757
|
-
const t = pc.addTransceiver('video');
|
|
758
|
-
expect(t.receiver.track.kind).toBe('video');
|
|
759
|
-
closePeerConnections(pc);
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
await it('sender track should be null initially', async () => {
|
|
763
|
-
const pc = createPeerConnection();
|
|
764
|
-
const t = pc.addTransceiver('audio');
|
|
765
|
-
expect(t.sender.track).toBeNull();
|
|
766
|
-
closePeerConnections(pc);
|
|
767
|
-
});
|
|
768
|
-
|
|
769
|
-
await it('sender dtmf should be null', async () => {
|
|
770
|
-
const pc = createPeerConnection();
|
|
771
|
-
const t = pc.addTransceiver('audio');
|
|
772
|
-
expect(t.sender.dtmf).toBeNull();
|
|
773
|
-
closePeerConnections(pc);
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
await it('receiver transport should be null', async () => {
|
|
777
|
-
const pc = createPeerConnection();
|
|
778
|
-
const t = pc.addTransceiver('audio');
|
|
779
|
-
expect(t.receiver.transport).toBeNull();
|
|
780
|
-
closePeerConnections(pc);
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
await it('sender transport should be null', async () => {
|
|
784
|
-
const pc = createPeerConnection();
|
|
785
|
-
const t = pc.addTransceiver('audio');
|
|
786
|
-
expect(t.sender.transport).toBeNull();
|
|
787
|
-
closePeerConnections(pc);
|
|
788
|
-
});
|
|
789
|
-
});
|
|
790
|
-
|
|
791
|
-
// ==========================================================================
|
|
792
|
-
// Receiver media flow (Phase 2.5)
|
|
793
|
-
// ==========================================================================
|
|
794
|
-
await describe('Receiver media pipeline (Phase 2.5)', async () => {
|
|
795
|
-
await it('receiver has _connectToPad method', async () => {
|
|
796
|
-
const pc = createPeerConnection();
|
|
797
|
-
const t = pc.addTransceiver('audio');
|
|
798
|
-
expect(typeof t.receiver._connectToPad).toBe('function');
|
|
799
|
-
expect(typeof t.receiver._dispose).toBe('function');
|
|
800
|
-
closePeerConnections(pc);
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
await it('receiver track starts muted', async () => {
|
|
804
|
-
const pc = createPeerConnection();
|
|
805
|
-
const t = pc.addTransceiver('audio');
|
|
806
|
-
expect(t.receiver.track.muted).toBe(true);
|
|
807
|
-
closePeerConnections(pc);
|
|
808
|
-
});
|
|
809
|
-
|
|
810
|
-
await it('_setMuted(false) dispatches unmute event', async () => {
|
|
811
|
-
const track = new MediaStreamTrack({ kind: 'audio', muted: true });
|
|
812
|
-
expect(track.muted).toBe(true);
|
|
813
|
-
let unmuted = false;
|
|
814
|
-
track.addEventListener('unmute', () => { unmuted = true; });
|
|
815
|
-
track._setMuted(false);
|
|
816
|
-
expect(track.muted).toBe(false);
|
|
817
|
-
expect(unmuted).toBe(true);
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
await it('_setMuted(true) dispatches mute event', async () => {
|
|
821
|
-
const track = new MediaStreamTrack({ kind: 'audio', muted: false });
|
|
822
|
-
let muted = false;
|
|
823
|
-
track.addEventListener('mute', () => { muted = true; });
|
|
824
|
-
track._setMuted(true);
|
|
825
|
-
expect(track.muted).toBe(true);
|
|
826
|
-
expect(muted).toBe(true);
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
await it('close disposes receiver bridges without error', async () => {
|
|
830
|
-
const pc = createPeerConnection();
|
|
831
|
-
pc.addTransceiver('audio');
|
|
832
|
-
pc.addTransceiver('video');
|
|
833
|
-
pc.close();
|
|
834
|
-
});
|
|
835
|
-
});
|
|
836
|
-
|
|
837
|
-
// ── Phase 3: addTrack ──────────────────────────────────────────────────
|
|
838
|
-
|
|
839
|
-
await describe('addTrack (Phase 3)', async () => {
|
|
840
|
-
await it('should return a sender with the track assigned', async () => {
|
|
841
|
-
const pc = createPeerConnection();
|
|
842
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
843
|
-
const sender = pc.addTrack(track);
|
|
844
|
-
expect(sender).toBeDefined();
|
|
845
|
-
expect(sender.track).toBe(track);
|
|
846
|
-
closePeerConnections(pc);
|
|
847
|
-
});
|
|
848
|
-
|
|
849
|
-
await it('should throw TypeError for non-MediaStreamTrack argument', async () => {
|
|
850
|
-
const pc = createPeerConnection();
|
|
851
|
-
expect(() => (pc as any).addTrack('audio')).toThrow();
|
|
852
|
-
expect(() => (pc as any).addTrack({})).toThrow();
|
|
853
|
-
closePeerConnections(pc);
|
|
854
|
-
});
|
|
855
|
-
|
|
856
|
-
await it('should throw InvalidAccessError if track already added', async () => {
|
|
857
|
-
const pc = createPeerConnection();
|
|
858
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
859
|
-
pc.addTrack(track);
|
|
860
|
-
let threw = false;
|
|
861
|
-
try {
|
|
862
|
-
pc.addTrack(track);
|
|
863
|
-
} catch (e: any) {
|
|
864
|
-
threw = true;
|
|
865
|
-
expect(e.name).toBe('InvalidAccessError');
|
|
866
|
-
}
|
|
867
|
-
expect(threw).toBe(true);
|
|
868
|
-
closePeerConnections(pc);
|
|
869
|
-
});
|
|
870
|
-
|
|
871
|
-
await it('should reuse inactive transceiver with matching kind', async () => {
|
|
872
|
-
const pc = createPeerConnection();
|
|
873
|
-
pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
874
|
-
expect(pc.getTransceivers().length).toBe(1);
|
|
875
|
-
|
|
876
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
877
|
-
const sender = pc.addTrack(track);
|
|
878
|
-
// Should reuse, not create a new transceiver
|
|
879
|
-
expect(pc.getTransceivers().length).toBe(1);
|
|
880
|
-
expect(sender.track).toBe(track);
|
|
881
|
-
closePeerConnections(pc);
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
await it('should create new transceiver when no reusable one exists', async () => {
|
|
885
|
-
const pc = createPeerConnection();
|
|
886
|
-
// sendrecv transceiver already has send — not reusable
|
|
887
|
-
pc.addTransceiver('audio', { direction: 'sendrecv' });
|
|
888
|
-
expect(pc.getTransceivers().length).toBe(1);
|
|
889
|
-
|
|
890
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
891
|
-
pc.addTrack(track);
|
|
892
|
-
expect(pc.getTransceivers().length).toBe(2);
|
|
893
|
-
closePeerConnections(pc);
|
|
894
|
-
});
|
|
895
|
-
|
|
896
|
-
await it('should create transceiver with sendrecv direction', async () => {
|
|
897
|
-
const pc = createPeerConnection();
|
|
898
|
-
const track = new MediaStreamTrack({ kind: 'video' });
|
|
899
|
-
pc.addTrack(track);
|
|
900
|
-
const trans = pc.getTransceivers();
|
|
901
|
-
expect(trans.length).toBe(1);
|
|
902
|
-
expect(trans[0].direction).toBe('sendrecv');
|
|
903
|
-
closePeerConnections(pc);
|
|
904
|
-
});
|
|
905
|
-
|
|
906
|
-
await it('should add sender to getSenders list', async () => {
|
|
907
|
-
const pc = createPeerConnection();
|
|
908
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
909
|
-
const sender = pc.addTrack(track);
|
|
910
|
-
expect(pc.getSenders()).toContain(sender);
|
|
911
|
-
closePeerConnections(pc);
|
|
912
|
-
});
|
|
913
|
-
|
|
914
|
-
await it('should throw InvalidStateError after close', async () => {
|
|
915
|
-
const pc = createPeerConnection();
|
|
916
|
-
pc.close();
|
|
917
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
918
|
-
expect(() => pc.addTrack(track)).toThrow();
|
|
919
|
-
});
|
|
920
|
-
});
|
|
921
|
-
|
|
922
|
-
// ── Phase 3: removeTrack ───────────────────────────────────────────────
|
|
923
|
-
|
|
924
|
-
await describe('removeTrack (Phase 3)', async () => {
|
|
925
|
-
await it('should set sender.track to null', async () => {
|
|
926
|
-
const pc = createPeerConnection();
|
|
927
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
928
|
-
const sender = pc.addTrack(track);
|
|
929
|
-
pc.removeTrack(sender);
|
|
930
|
-
expect(sender.track).toBeNull();
|
|
931
|
-
closePeerConnections(pc);
|
|
932
|
-
});
|
|
933
|
-
|
|
934
|
-
await it('should throw for sender from different connection', async () => {
|
|
935
|
-
const pc1 = createPeerConnection();
|
|
936
|
-
const pc2 = createPeerConnection();
|
|
937
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
938
|
-
const sender = pc1.addTrack(track);
|
|
939
|
-
expect(() => pc2.removeTrack(sender)).toThrow();
|
|
940
|
-
closePeerConnections(pc1, pc2);
|
|
941
|
-
});
|
|
942
|
-
});
|
|
943
|
-
|
|
944
|
-
// ── Phase 3: replaceTrack ──────────────────────────────────────────────
|
|
945
|
-
|
|
946
|
-
await describe('replaceTrack (Phase 3)', async () => {
|
|
947
|
-
await it('should replace track on sender', async () => {
|
|
948
|
-
const pc = createPeerConnection();
|
|
949
|
-
const track1 = new MediaStreamTrack({ kind: 'audio' });
|
|
950
|
-
const sender = pc.addTrack(track1);
|
|
951
|
-
const track2 = new MediaStreamTrack({ kind: 'audio' });
|
|
952
|
-
await sender.replaceTrack(track2);
|
|
953
|
-
expect(sender.track).toBe(track2);
|
|
954
|
-
closePeerConnections(pc);
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
await it('should accept null to remove track', async () => {
|
|
958
|
-
const pc = createPeerConnection();
|
|
959
|
-
const track = new MediaStreamTrack({ kind: 'audio' });
|
|
960
|
-
const sender = pc.addTrack(track);
|
|
961
|
-
await sender.replaceTrack(null);
|
|
962
|
-
expect(sender.track).toBeNull();
|
|
963
|
-
closePeerConnections(pc);
|
|
964
|
-
});
|
|
965
|
-
|
|
966
|
-
await it('should throw for different kind', async () => {
|
|
967
|
-
const pc = createPeerConnection();
|
|
968
|
-
const audio = new MediaStreamTrack({ kind: 'audio' });
|
|
969
|
-
const sender = pc.addTrack(audio);
|
|
970
|
-
const video = new MediaStreamTrack({ kind: 'video' });
|
|
971
|
-
let threw = false;
|
|
972
|
-
try {
|
|
973
|
-
await sender.replaceTrack(video);
|
|
974
|
-
} catch {
|
|
975
|
-
threw = true;
|
|
976
|
-
}
|
|
977
|
-
expect(threw).toBe(true);
|
|
978
|
-
closePeerConnections(pc);
|
|
979
|
-
});
|
|
980
|
-
});
|
|
981
|
-
|
|
982
|
-
// ── Phase 3: getUserMedia ──────────────────────────────────────────────
|
|
983
|
-
|
|
984
|
-
await describe('getUserMedia (Phase 3)', async () => {
|
|
985
|
-
const { getUserMedia } = await import('./get-user-media.js');
|
|
986
|
-
|
|
987
|
-
await it('should return MediaStream with audio track', async () => {
|
|
988
|
-
const stream = await getUserMedia({ audio: true });
|
|
989
|
-
expect(stream).toBeDefined();
|
|
990
|
-
expect(stream.getAudioTracks().length).toBe(1);
|
|
991
|
-
expect(stream.getVideoTracks().length).toBe(0);
|
|
992
|
-
const track = stream.getAudioTracks()[0];
|
|
993
|
-
expect(track.kind).toBe('audio');
|
|
994
|
-
expect(track.readyState).toBe('live');
|
|
995
|
-
stream.getTracks().forEach(t => t.stop());
|
|
996
|
-
});
|
|
997
|
-
|
|
998
|
-
await it('should return MediaStream with video track', async () => {
|
|
999
|
-
const stream = await getUserMedia({ video: true });
|
|
1000
|
-
expect(stream).toBeDefined();
|
|
1001
|
-
expect(stream.getVideoTracks().length).toBe(1);
|
|
1002
|
-
const track = stream.getVideoTracks()[0];
|
|
1003
|
-
expect(track.kind).toBe('video');
|
|
1004
|
-
stream.getTracks().forEach(t => t.stop());
|
|
1005
|
-
});
|
|
1006
|
-
|
|
1007
|
-
await it('should return MediaStream with both audio and video', async () => {
|
|
1008
|
-
const stream = await getUserMedia({ audio: true, video: true });
|
|
1009
|
-
expect(stream.getAudioTracks().length).toBe(1);
|
|
1010
|
-
expect(stream.getVideoTracks().length).toBe(1);
|
|
1011
|
-
stream.getTracks().forEach(t => t.stop());
|
|
1012
|
-
});
|
|
1013
|
-
|
|
1014
|
-
await it('should throw TypeError without audio or video', async () => {
|
|
1015
|
-
let threw = false;
|
|
1016
|
-
try {
|
|
1017
|
-
await getUserMedia({});
|
|
1018
|
-
} catch (e: any) {
|
|
1019
|
-
threw = true;
|
|
1020
|
-
}
|
|
1021
|
-
expect(threw).toBe(true);
|
|
1022
|
-
});
|
|
1023
|
-
|
|
1024
|
-
await it('track should have GStreamer source backing', async () => {
|
|
1025
|
-
const stream = await getUserMedia({ audio: true });
|
|
1026
|
-
const track = stream.getAudioTracks()[0];
|
|
1027
|
-
expect((track as any)._gstSource).toBeDefined();
|
|
1028
|
-
expect((track as any)._gstSource).not.toBeNull();
|
|
1029
|
-
stream.getTracks().forEach(t => t.stop());
|
|
1030
|
-
});
|
|
1031
|
-
|
|
1032
|
-
await it('stop should set readyState to ended and dispatch event', async () => {
|
|
1033
|
-
const stream = await getUserMedia({ audio: true });
|
|
1034
|
-
const track = stream.getAudioTracks()[0];
|
|
1035
|
-
let ended = false;
|
|
1036
|
-
track.addEventListener('ended', () => { ended = true; });
|
|
1037
|
-
track.stop();
|
|
1038
|
-
expect(track.readyState).toBe('ended');
|
|
1039
|
-
expect(ended).toBe(true);
|
|
1040
|
-
});
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
|
-
// ── Phase 3: MediaDevices ──────────────────────────────────────────────
|
|
1044
|
-
|
|
1045
|
-
await describe('MediaDevices (Phase 3)', async () => {
|
|
1046
|
-
const { MediaDevices } = await import('./media-devices.js');
|
|
1047
|
-
|
|
1048
|
-
await it('should have getUserMedia method', async () => {
|
|
1049
|
-
const md = new MediaDevices();
|
|
1050
|
-
expect(typeof md.getUserMedia).toBe('function');
|
|
1051
|
-
});
|
|
1052
|
-
|
|
1053
|
-
await it('should have enumerateDevices method', async () => {
|
|
1054
|
-
const md = new MediaDevices();
|
|
1055
|
-
const devices = await md.enumerateDevices();
|
|
1056
|
-
expect(Array.isArray(devices)).toBe(true);
|
|
1057
|
-
});
|
|
1058
|
-
|
|
1059
|
-
await it('should have getSupportedConstraints method', async () => {
|
|
1060
|
-
const md = new MediaDevices();
|
|
1061
|
-
const sc = md.getSupportedConstraints();
|
|
1062
|
-
expect(typeof sc).toBe('object');
|
|
1063
|
-
});
|
|
1064
|
-
|
|
1065
|
-
await it('getUserMedia should throw without constraints', async () => {
|
|
1066
|
-
const md = new MediaDevices();
|
|
1067
|
-
let threw = false;
|
|
1068
|
-
try {
|
|
1069
|
-
await md.getUserMedia();
|
|
1070
|
-
} catch {
|
|
1071
|
-
threw = true;
|
|
1072
|
-
}
|
|
1073
|
-
expect(threw).toBe(true);
|
|
1074
|
-
});
|
|
1075
|
-
});
|
|
1076
|
-
|
|
1077
|
-
// ── RTCRtpParameters structure validation ────────────────────────────
|
|
1078
|
-
// Ported from refs/wpt/webrtc/RTCRtpParameters-codecs.html,
|
|
1079
|
-
// RTCRtpParameters-encodings.html, RTCRtpParameters-headerExtensions.html
|
|
1080
|
-
|
|
1081
|
-
await describe('RTCRtpParameters structure (WPT)', async () => {
|
|
1082
|
-
await it('getParameters().encodings entries have active boolean', async () => {
|
|
1083
|
-
const pc = createPeerConnection();
|
|
1084
|
-
try {
|
|
1085
|
-
const t = pc.addTransceiver('audio');
|
|
1086
|
-
const params = t.sender.getParameters();
|
|
1087
|
-
expect(Array.isArray(params.encodings)).toBeTruthy();
|
|
1088
|
-
for (const enc of params.encodings) {
|
|
1089
|
-
expect(typeof enc.active).toBe('boolean');
|
|
1090
|
-
}
|
|
1091
|
-
} finally { pc.close(); }
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
|
-
await it('getParameters().codecs entries have mimeType, clockRate, payloadType', async () => {
|
|
1095
|
-
const pc1 = createPeerConnection();
|
|
1096
|
-
const pc2 = createPeerConnection();
|
|
1097
|
-
try {
|
|
1098
|
-
pc1.addTransceiver('audio');
|
|
1099
|
-
await exchangeOfferAnswer(pc1, pc2);
|
|
1100
|
-
const sender = pc1.getSenders()[0];
|
|
1101
|
-
const params = sender.getParameters();
|
|
1102
|
-
expect(params.codecs.length).toBeGreaterThan(0);
|
|
1103
|
-
for (const codec of params.codecs) {
|
|
1104
|
-
expect(typeof codec.mimeType).toBe('string');
|
|
1105
|
-
expect(typeof codec.clockRate).toBe('number');
|
|
1106
|
-
expect(typeof codec.payloadType).toBe('number');
|
|
1107
|
-
}
|
|
1108
|
-
} finally { closePeerConnections(pc1, pc2); }
|
|
1109
|
-
});
|
|
1110
|
-
|
|
1111
|
-
await it('getParameters().headerExtensions entries have uri and id', async () => {
|
|
1112
|
-
const pc1 = createPeerConnection();
|
|
1113
|
-
const pc2 = createPeerConnection();
|
|
1114
|
-
try {
|
|
1115
|
-
pc1.addTransceiver('audio');
|
|
1116
|
-
await exchangeOfferAnswer(pc1, pc2);
|
|
1117
|
-
const sender = pc1.getSenders()[0];
|
|
1118
|
-
const params = sender.getParameters();
|
|
1119
|
-
// headerExtensions may be empty before negotiation on some impls
|
|
1120
|
-
for (const ext of params.headerExtensions) {
|
|
1121
|
-
expect(typeof ext.uri).toBe('string');
|
|
1122
|
-
expect(typeof ext.id).toBe('number');
|
|
1123
|
-
}
|
|
1124
|
-
} finally { closePeerConnections(pc1, pc2); }
|
|
1125
|
-
});
|
|
1126
|
-
|
|
1127
|
-
await it('after negotiation codecs array is non-empty', async () => {
|
|
1128
|
-
const pc1 = createPeerConnection();
|
|
1129
|
-
const pc2 = createPeerConnection();
|
|
1130
|
-
try {
|
|
1131
|
-
pc1.addTransceiver('audio');
|
|
1132
|
-
await exchangeOfferAnswer(pc1, pc2);
|
|
1133
|
-
const sender = pc1.getSenders()[0];
|
|
1134
|
-
const params = sender.getParameters();
|
|
1135
|
-
expect(params.codecs.length).toBeGreaterThan(0);
|
|
1136
|
-
} finally { closePeerConnections(pc1, pc2); }
|
|
1137
|
-
});
|
|
1138
|
-
|
|
1139
|
-
await it('getParameters().rtcp has cname string', async () => {
|
|
1140
|
-
const pc1 = createPeerConnection();
|
|
1141
|
-
const pc2 = createPeerConnection();
|
|
1142
|
-
try {
|
|
1143
|
-
pc1.addTransceiver('audio');
|
|
1144
|
-
await exchangeOfferAnswer(pc1, pc2);
|
|
1145
|
-
const sender = pc1.getSenders()[0];
|
|
1146
|
-
const params = sender.getParameters();
|
|
1147
|
-
if (params.rtcp) {
|
|
1148
|
-
expect(typeof params.rtcp.cname).toBe('string');
|
|
1149
|
-
}
|
|
1150
|
-
} finally { closePeerConnections(pc1, pc2); }
|
|
1151
|
-
});
|
|
1152
|
-
});
|
|
1153
|
-
|
|
1154
|
-
};
|