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