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
|
(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);
|