blob: 817095e981f7724ba4d8a9f1cec26d2b4b7e3b4d [file] [log] [blame]
/**
* @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); });
}
}