300 lines
9.2 KiB
JavaScript
300 lines
9.2 KiB
JavaScript
// Test inspired from https://webrtc.github.io/samples/
|
|
var localConnection;
|
|
var remoteConnection;
|
|
|
|
function createConnections(setupLocalConnection, setupRemoteConnection, options = { }) {
|
|
localConnection = new RTCPeerConnection();
|
|
remoteConnection = new RTCPeerConnection();
|
|
remoteConnection.onicecandidate = (event) => { iceCallback2(event, options.filterOutICECandidate) };
|
|
|
|
localConnection.onicecandidate = (event) => { iceCallback1(event, options.filterOutICECandidate) };
|
|
|
|
Promise.resolve(setupLocalConnection(localConnection)).then(() => {
|
|
return Promise.resolve(setupRemoteConnection(remoteConnection));
|
|
}).then(() => {
|
|
localConnection.createOffer().then((desc) => gotDescription1(desc, options), onCreateSessionDescriptionError);
|
|
});
|
|
|
|
return [localConnection, remoteConnection]
|
|
}
|
|
|
|
function closeConnections()
|
|
{
|
|
localConnection.close();
|
|
remoteConnection.close();
|
|
}
|
|
|
|
function onCreateSessionDescriptionError(error)
|
|
{
|
|
assert_unreached();
|
|
}
|
|
|
|
function gotDescription1(desc, options)
|
|
{
|
|
if (options.observeOffer) {
|
|
const result = options.observeOffer(desc);
|
|
if (result)
|
|
desc = result;
|
|
}
|
|
|
|
localConnection.setLocalDescription(desc);
|
|
remoteConnection.setRemoteDescription(desc).then(() => {
|
|
remoteConnection.createAnswer().then((desc) => gotDescription2(desc, options), onCreateSessionDescriptionError);
|
|
});
|
|
}
|
|
|
|
function gotDescription2(desc, options)
|
|
{
|
|
if (options.observeAnswer)
|
|
options.observeAnswer(desc);
|
|
|
|
remoteConnection.setLocalDescription(desc);
|
|
localConnection.setRemoteDescription(desc);
|
|
}
|
|
|
|
function iceCallback1(event, filterOutICECandidate)
|
|
{
|
|
if (filterOutICECandidate && filterOutICECandidate(event.candidate))
|
|
return;
|
|
|
|
remoteConnection.addIceCandidate(event.candidate).then(onAddIceCandidateSuccess, onAddIceCandidateError);
|
|
}
|
|
|
|
function iceCallback2(event, filterOutICECandidate)
|
|
{
|
|
if (filterOutICECandidate && filterOutICECandidate(event.candidate))
|
|
return;
|
|
|
|
localConnection.addIceCandidate(event.candidate).then(onAddIceCandidateSuccess, onAddIceCandidateError);
|
|
}
|
|
|
|
function onAddIceCandidateSuccess()
|
|
{
|
|
}
|
|
|
|
function onAddIceCandidateError(error)
|
|
{
|
|
console.log("addIceCandidate error: " + error)
|
|
assert_unreached();
|
|
}
|
|
|
|
async function renegotiate(pc1, pc2)
|
|
{
|
|
let d = await pc1.createOffer();
|
|
await pc1.setLocalDescription(d);
|
|
await pc2.setRemoteDescription(d);
|
|
d = await pc2.createAnswer();
|
|
await pc1.setRemoteDescription(d);
|
|
await pc2.setLocalDescription(d);
|
|
}
|
|
|
|
function analyseAudio(stream, duration, context)
|
|
{
|
|
return new Promise((resolve, reject) => {
|
|
var sourceNode = context.createMediaStreamSource(stream);
|
|
|
|
var analyser = context.createAnalyser();
|
|
var gain = context.createGain();
|
|
|
|
var results = { heardHum: false, heardBip: false, heardBop: false, heardNoise: false };
|
|
|
|
analyser.fftSize = 2048;
|
|
analyser.smoothingTimeConstant = 0;
|
|
analyser.minDecibels = -100;
|
|
analyser.maxDecibels = 0;
|
|
gain.gain.value = 0;
|
|
|
|
sourceNode.connect(analyser);
|
|
analyser.connect(gain);
|
|
gain.connect(context.destination);
|
|
|
|
function analyse() {
|
|
var freqDomain = new Uint8Array(analyser.frequencyBinCount);
|
|
analyser.getByteFrequencyData(freqDomain);
|
|
|
|
var hasFrequency = expectedFrequency => {
|
|
var bin = Math.floor(expectedFrequency * analyser.fftSize / context.sampleRate);
|
|
return bin < freqDomain.length && freqDomain[bin] >= 100;
|
|
};
|
|
|
|
if (!results.heardHum)
|
|
results.heardHum = hasFrequency(150);
|
|
|
|
if (!results.heardBip)
|
|
results.heardBip = hasFrequency(1500);
|
|
|
|
if (!results.heardBop)
|
|
results.heardBop = hasFrequency(500);
|
|
|
|
if (!results.heardNoise)
|
|
results.heardNoise = hasFrequency(3000);
|
|
|
|
if (results.heardHum && results.heardBip && results.heardBop && results.heardNoise)
|
|
done();
|
|
};
|
|
|
|
function done() {
|
|
clearTimeout(timeout);
|
|
clearInterval(interval);
|
|
resolve(results);
|
|
}
|
|
|
|
var timeout = setTimeout(done, 3 * duration);
|
|
var interval = setInterval(analyse, duration / 30);
|
|
analyse();
|
|
});
|
|
}
|
|
|
|
function waitFor(duration)
|
|
{
|
|
return new Promise((resolve) => setTimeout(resolve, duration));
|
|
}
|
|
|
|
function waitForVideoSize(video, width, height, count)
|
|
{
|
|
if (video.videoWidth === width && video.videoHeight === height)
|
|
return Promise.resolve("video has expected size");
|
|
|
|
if (count === undefined)
|
|
count = 0;
|
|
if (++count > 20)
|
|
return Promise.reject("waitForVideoSize timed out, expected " + width + "x"+ height + " but got " + video.videoWidth + "x" + video.videoHeight);
|
|
|
|
return waitFor(100).then(() => {
|
|
return waitForVideoSize(video, width, height, count);
|
|
});
|
|
}
|
|
|
|
async function doHumAnalysis(stream, expected)
|
|
{
|
|
var context = new AudioContext();
|
|
for (var cptr = 0; cptr < 20; cptr++) {
|
|
var results = await analyseAudio(stream, 200, context);
|
|
if (results.heardHum === expected)
|
|
return true;
|
|
await waitFor(50);
|
|
}
|
|
await context.close();
|
|
return false;
|
|
}
|
|
|
|
function isVideoBlack(canvas, video, startX, startY, grabbedWidth, grabbedHeight)
|
|
{
|
|
canvas.width = video.videoWidth;
|
|
canvas.height = video.videoHeight;
|
|
if (!grabbedHeight) {
|
|
startX = 10;
|
|
startY = 10;
|
|
grabbedWidth = canvas.width - 20;
|
|
grabbedHeight = canvas.height - 20;
|
|
}
|
|
|
|
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
|
|
imageData = canvas.getContext('2d').getImageData(startX, startY, grabbedWidth, grabbedHeight);
|
|
data = imageData.data;
|
|
for (var cptr = 0; cptr < grabbedWidth * grabbedHeight; ++cptr) {
|
|
// Approximatively black pixels.
|
|
if (data[4 * cptr] > 30 || data[4 * cptr + 1] > 30 || data[4 * cptr + 2] > 30)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async function checkVideoBlack(expected, canvas, video, errorMessage, counter)
|
|
{
|
|
if (isVideoBlack(canvas, video) === expected)
|
|
return Promise.resolve();
|
|
|
|
if (counter === undefined)
|
|
counter = 0;
|
|
if (counter > 400) {
|
|
if (!errorMessage)
|
|
errorMessage = "checkVideoBlack timed out expecting " + expected;
|
|
return Promise.reject(errorMessage);
|
|
}
|
|
|
|
await waitFor(50);
|
|
return checkVideoBlack(expected, canvas, video, errorMessage, ++counter);
|
|
}
|
|
|
|
function setCodec(sdp, codec)
|
|
{
|
|
return sdp.split('\r\n').filter(line => {
|
|
return line.indexOf('a=fmtp') === -1 && line.indexOf('a=rtcp-fb') === -1 && (line.indexOf('a=rtpmap') === -1 || line.indexOf(codec) !== -1);
|
|
}).join('\r\n');
|
|
}
|
|
|
|
async function getTypedStats(connection, type)
|
|
{
|
|
const report = await connection.getStats();
|
|
var stats;
|
|
report.forEach((statItem) => {
|
|
if (statItem.type === type)
|
|
stats = statItem;
|
|
});
|
|
return stats;
|
|
}
|
|
|
|
function getReceivedTrackStats(connection)
|
|
{
|
|
return connection.getStats().then((report) => {
|
|
var stats;
|
|
report.forEach((statItem) => {
|
|
if (statItem.type === "track") {
|
|
stats = statItem;
|
|
}
|
|
});
|
|
return stats;
|
|
});
|
|
}
|
|
|
|
async function computeFrameRate(stream, video)
|
|
{
|
|
if (window.internals) {
|
|
internals.observeMediaStreamTrack(stream.getVideoTracks()[0]);
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
return internals.trackVideoSampleCount;
|
|
}
|
|
|
|
let connection;
|
|
video.srcObject = await new Promise((resolve, reject) => {
|
|
createConnections((firstConnection) => {
|
|
firstConnection.addTrack(stream.getVideoTracks()[0], stream);
|
|
}, (secondConnection) => {
|
|
connection = secondConnection;
|
|
secondConnection.ontrack = (trackEvent) => {
|
|
resolve(trackEvent.streams[0]);
|
|
};
|
|
});
|
|
setTimeout(() => reject("Test timed out"), 5000);
|
|
});
|
|
|
|
await video.play();
|
|
|
|
const stats1 = await getReceivedTrackStats(connection);
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
const stats2 = await getReceivedTrackStats(connection);
|
|
return (stats2.framesReceived - stats1.framesReceived) * 1000 / (stats2.timestamp - stats1.timestamp);
|
|
}
|
|
|
|
function setH264BaselineCodec(sdp)
|
|
{
|
|
const lines = sdp.split('\r\n');
|
|
const h264Lines = lines.filter(line => line.indexOf("a=fmtp") === 0 && line.indexOf("42e01f") !== -1);
|
|
const baselineNumber = h264Lines[0].substring(6).split(' ')[0];
|
|
return lines.filter(line => {
|
|
return (line.indexOf('a=fmtp') === -1 && line.indexOf('a=rtcp-fb') === -1 && line.indexOf('a=rtpmap') === -1) || line.indexOf(baselineNumber) !== -1;
|
|
}).join('\r\n');
|
|
}
|
|
|
|
function setH264HighCodec(sdp)
|
|
{
|
|
const lines = sdp.split('\r\n');
|
|
const h264Lines = lines.filter(line => line.indexOf("a=fmtp") === 0 && line.indexOf("640c1f") !== -1);
|
|
const baselineNumber = h264Lines[0].substring(6).split(' ')[0];
|
|
return lines.filter(line => {
|
|
return (line.indexOf('a=fmtp') === -1 && line.indexOf('a=rtcp-fb') === -1 && line.indexOf('a=rtpmap') === -1) || line.indexOf(baselineNumber) !== -1;
|
|
}).join('\r\n');
|
|
}
|