134 lines
3.9 KiB
JavaScript
134 lines
3.9 KiB
JavaScript
(function (global) {
|
|
"use strict";
|
|
|
|
// an invertible check on the condition.
|
|
// if the constraint is applied, then the check is direct
|
|
// if not applied, then the result should be reversed
|
|
function check(constraintApplied, condition, message) {
|
|
var good = constraintApplied ? condition : !condition;
|
|
message =
|
|
(constraintApplied ? "with" : "without") +
|
|
" constraint: should " +
|
|
(constraintApplied ? "" : "not ") +
|
|
message +
|
|
" = " +
|
|
(good ? "OK" : "waiting...");
|
|
info(message);
|
|
return good;
|
|
}
|
|
|
|
function mkElement(type) {
|
|
// This makes an unattached element.
|
|
// It's not rendered to save the cycles that costs on b2g emulator
|
|
// and it gets dropped (and GC'd) when the test is done.
|
|
var e = document.createElement(type);
|
|
e.width = 32;
|
|
e.height = 24;
|
|
document.getElementById("display").appendChild(e);
|
|
return e;
|
|
}
|
|
|
|
// Runs checkFunc until it reports success.
|
|
// This is kludgy, but you have to wait for media to start flowing, and it
|
|
// can't be any old media, it has to include real data, for which we have no
|
|
// reliable signals to use as a trigger.
|
|
function periodicCheck(checkFunc) {
|
|
var resolve;
|
|
var done = false;
|
|
// This returns a function so that we create 10 closures in the loop, not
|
|
// one; and so that the timers don't all start straight away
|
|
var waitAndCheck = counter => () => {
|
|
if (done) {
|
|
return Promise.resolve();
|
|
}
|
|
return new Promise(r => setTimeout(r, 200 << counter)).then(() => {
|
|
if (checkFunc()) {
|
|
done = true;
|
|
resolve();
|
|
}
|
|
});
|
|
};
|
|
|
|
var chain = Promise.resolve();
|
|
for (var i = 0; i < 10; ++i) {
|
|
chain = chain.then(waitAndCheck(i));
|
|
}
|
|
return new Promise(r => (resolve = r));
|
|
}
|
|
|
|
function isSilence(audioData) {
|
|
var silence = true;
|
|
for (var i = 0; i < audioData.length; ++i) {
|
|
if (audioData[i] !== 128) {
|
|
silence = false;
|
|
}
|
|
}
|
|
return silence;
|
|
}
|
|
|
|
function checkAudio(constraintApplied, stream) {
|
|
var audio = mkElement("audio");
|
|
audio.srcObject = stream;
|
|
audio.play();
|
|
|
|
var context = new AudioContext();
|
|
var source = context.createMediaStreamSource(stream);
|
|
var analyser = context.createAnalyser();
|
|
source.connect(analyser);
|
|
analyser.connect(context.destination);
|
|
|
|
return periodicCheck(() => {
|
|
var sampleCount = analyser.frequencyBinCount;
|
|
info("got some audio samples: " + sampleCount);
|
|
var buffer = new Uint8Array(sampleCount);
|
|
analyser.getByteTimeDomainData(buffer);
|
|
|
|
var silent = check(
|
|
constraintApplied,
|
|
isSilence(buffer),
|
|
"be silence for audio"
|
|
);
|
|
return sampleCount > 0 && silent;
|
|
}).then(() => {
|
|
source.disconnect();
|
|
analyser.disconnect();
|
|
audio.pause();
|
|
ok(true, "audio is " + (constraintApplied ? "" : "not ") + "silent");
|
|
});
|
|
}
|
|
|
|
function checkVideo(constraintApplied, stream) {
|
|
var video = mkElement("video");
|
|
video.srcObject = stream;
|
|
video.play();
|
|
|
|
return periodicCheck(() => {
|
|
try {
|
|
var canvas = mkElement("canvas");
|
|
var ctx = canvas.getContext("2d");
|
|
// Have to guard drawImage with the try as well, due to bug 879717. If
|
|
// we get an error, this round fails, but that failure is usually just
|
|
// transitory.
|
|
ctx.drawImage(video, 0, 0);
|
|
ctx.getImageData(0, 0, 1, 1);
|
|
return check(
|
|
constraintApplied,
|
|
false,
|
|
"throw on getImageData for video"
|
|
);
|
|
} catch (e) {
|
|
return check(
|
|
constraintApplied,
|
|
e.name === "SecurityError",
|
|
"get a security error: " + e.name
|
|
);
|
|
}
|
|
}).then(() => {
|
|
video.pause();
|
|
ok(true, "video is " + (constraintApplied ? "" : "not ") + "protected");
|
|
});
|
|
}
|
|
|
|
global.audioIsSilence = checkAudio;
|
|
global.videoIsBlack = checkVideo;
|
|
})(this);
|