-
Notifications
You must be signed in to change notification settings - Fork 54
/
peerconnection.js
156 lines (151 loc) · 5.47 KB
/
peerconnection.js
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
* @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 (let 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} useAudio True if audio should be used.
* @param {boolean} useVideo 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 the list of transceivers.
*/
async function startConnection(t, useAudio, useVideo, videoCodecPreference) {
const stream = await navigator.mediaDevices.getUserMedia({
audio: useAudio, video: useVideo
});
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());
let transceivers = {};
stream.getTracks().forEach(track => {
const transceiver = pc1.addTransceiver(track);
transceivers[track.kind] = transceiver;
if (videoCodecPreference && track.kind == 'video') {
setTransceiverCodecPreference(transceiver, videoCodecPreference);
}
});
function doExchange(localPc, remotePc) {
localPc.addEventListener('icecandidate', event => {
const { candidate } = event;
if (candidate && remotePc.signalingState !== 'closed') {
remotePc.addIceCandidate(candidate);
}
});
}
doExchange(pc1, pc2);
doExchange(pc2, pc1);
exchangeOfferAnswer(pc1, pc2);
const remoteStream = await new Promise(resolve => {
let tracks = [];
pc2.ontrack = e => {
tracks.push(e.track)
if (tracks.length < useAudio + useVideo) return;
const stream = new MediaStream(tracks);
// The srcObject sink is needed for the tests to get exercised in Chrome.
const remoteVideo = document.getElementById('remote');
if (remoteVideo) {
remoteVideo.srcObject = stream;
}
resolve(stream)
}
});
return [pc1, pc2, remoteStream, 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 waitForReceivedFrames(
t, pc, lookForAudio, lookForVideo, numFramesOrPackets) {
let initialAudioPackets = 0;
let initialVideoFrames = 0;
while (lookForAudio || lookForVideo) {
const report = await pc.getStats();
report.forEach(stats => {
if (stats.type && 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();
report.forEach(stats => {
if (stats.id) {
if (stats.type == 'inbound-rtp' && stats.kind == 'video') {
currentCodecId = stats.codecId;
} else if (currentCodecId && stats.id == currentCodecId &&
stats.mimeType.toLowerCase().includes(
codecToLookFor.toLowerCase())) {
return;
}
}
});
await new Promise(r => { t.step_timeout(r, 100); });
}
}