1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
/**
* @fileoverview Utility functions for tests utilizing PeerConnections
*/
/**
* Exchanges offers and answers between two peer connections.
*
* pc1's offer is set as local description in pc1 and
* remote description in pc2. After that, pc2's answer
* is set as it's local description and remote description in pc1.
*
* @param {!RTCPeerConnection} pc1 The first peer connection.
* @param {!RTCPeerConnection} pc2 The second peer connection.
*/
async function exchangeOfferAnswer(pc1, pc2) {
await pc1.setLocalDescription(await pc1.createOffer());
await pc2.setRemoteDescription(pc1.localDescription);
await pc2.setLocalDescription(await pc2.createAnswer());
await pc1.setRemoteDescription(pc2.localDescription);
}
/**
* Sets the specified codec preference if it's included in the transceiver's
* list of supported codecs.
* @param {!RTCRtpTransceiver} transceiver The RTP transceiver.
* @param {string} codecPreference The codec preference.
*/
function setTransceiverCodecPreference(transceiver, codecPreference) {
for (const codec of RTCRtpSender.getCapabilities('video').codecs) {
if (codec.mimeType.includes(codecPreference)) {
transceiver.setCodecPreferences([codec]);
return;
}
}
}
/**
* Starts a connection between two peer connections, using a audio and/or video
* stream.
* @param {*} t Test instance.
* @param {boolean} audio True if audio should be used.
* @param {boolean} video True if video should be used.
* @param {string} [videoCodecPreference] String containing the codec preference.
* @returns an array with the two connected peer connections, the remote stream,
* and an object containing transceivers by kind.
*/
async function startConnection(t, audio, video, videoCodecPreference) {
const scope = [];
if (audio) scope.push("microphone");
if (video) scope.push("camera");
await setMediaPermission("granted", scope);
const stream = await navigator.mediaDevices.getUserMedia({audio, video});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());
const transceivers = {};
for (const track of stream.getTracks()) {
const transceiver = pc1.addTransceiver(track, {streams: [stream]});
transceivers[track.kind] = transceiver;
if (videoCodecPreference && track.kind == 'video') {
setTransceiverCodecPreference(transceiver, videoCodecPreference);
}
}
for (const [local, remote] of [[pc1, pc2], [pc2, pc1]]) {
local.addEventListener('icecandidate', ({candidate}) => {
if (!candidate || remote.signalingState == 'closed') return;
remote.addIceCandidate(candidate);
});
}
const haveTrackEvent = new Promise(r => pc2.ontrack = r);
await exchangeOfferAnswer(pc1, pc2);
const {streams} = await haveTrackEvent;
return [pc1, pc2, streams[0], transceivers];
}
/**
* Given a peer connection, return after at least numFramesOrPackets
* frames (video) or packets (audio) have been received.
* @param {*} t Test instance.
* @param {!RTCPeerConnection} pc The peer connection.
* @param {boolean} lookForAudio True if audio packets should be waited for.
* @param {boolean} lookForVideo True if video packets should be waited for.
* @param {int} numFramesOrPackets Number of frames (video) and packets (audio)
* to wait for.
*/
async function waitForReceivedFramesOrPackets(
t, pc, lookForAudio, lookForVideo, numFramesOrPackets) {
let initialAudioPackets = 0;
let initialVideoFrames = 0;
while (lookForAudio || lookForVideo) {
const report = await pc.getStats();
for (const stats of report.values()) {
if (stats.type == 'inbound-rtp') {
if (lookForAudio && stats.kind == 'audio') {
if (!initialAudioPackets) {
initialAudioPackets = stats.packetsReceived;
} else if (stats.packetsReceived > initialAudioPackets +
numFramesOrPackets) {
lookForAudio = false;
}
}
if (lookForVideo && stats.kind == 'video') {
if (!initialVideoFrames) {
initialVideoFrames = stats.framesDecoded;
} else if (stats.framesDecoded > initialVideoFrames +
numFramesOrPackets) {
lookForVideo = false;
}
}
}
}
await new Promise(r => t.step_timeout(r, 100));
}
}
/**
* Given a peer connection, return after one of its inbound RTP connections
* includes use of the specified codec.
* @param {*} t Test instance.
* @param {!RTCPeerConnection} pc The peer connection.
* @param {string} codecToLookFor The waited-for codec.
*/
async function waitForReceivedCodec(t, pc, codecToLookFor) {
let currentCodecId;
for (;;) {
const report = await pc.getStats();
for (const stats of report.values()) {
if (stats.type == 'inbound-rtp' && stats.kind == 'video') {
if (stats.codecId) {
if (report.get(stats.codecId).mimeType.toLowerCase()
.includes(codecToLookFor.toLowerCase())) {
return;
}
}
}
}
await new Promise(r => t.step_timeout(r, 100));
}
}
|