Make captureCanvas-webrtc.html more robust
[WebKit-https.git] / LayoutTests / webrtc / routines.js
1 // Test inspired from https://webrtc.github.io/samples/
2 var localConnection;
3 var remoteConnection;
4
5 function createConnections(setupLocalConnection, setupRemoteConnection, options = { }) {
6     localConnection = new RTCPeerConnection();
7     remoteConnection = new RTCPeerConnection();
8     remoteConnection.onicecandidate = (event) => { iceCallback2(event, options.filterOutICECandidate) };
9
10     localConnection.onicecandidate = (event) => { iceCallback1(event, options.filterOutICECandidate) };
11
12     Promise.resolve(setupLocalConnection(localConnection)).then(() => {
13         return Promise.resolve(setupRemoteConnection(remoteConnection));
14     }).then(() => {
15         localConnection.createOffer().then((desc) => gotDescription1(desc, options), onCreateSessionDescriptionError);
16     });
17
18     return [localConnection, remoteConnection]
19 }
20
21 function closeConnections()
22 {
23     localConnection.close();
24     remoteConnection.close();
25 }
26
27 function onCreateSessionDescriptionError(error)
28 {
29     assert_unreached();
30 }
31
32 function gotDescription1(desc, options)
33 {
34     if (options.observeOffer)
35         options.observeOffer(desc);
36
37     localConnection.setLocalDescription(desc);
38     remoteConnection.setRemoteDescription(desc).then(() => {
39         remoteConnection.createAnswer().then((desc) => gotDescription2(desc, options), onCreateSessionDescriptionError);
40     });
41 }
42
43 function gotDescription2(desc, options)
44 {
45     if (options.observeAnswer)
46         options.observeAnswer(desc);
47
48     remoteConnection.setLocalDescription(desc);
49     localConnection.setRemoteDescription(desc);
50 }
51
52 function iceCallback1(event, filterOutICECandidate)
53 {
54     if (filterOutICECandidate && filterOutICECandidate(event.candidate))
55         return;
56
57     remoteConnection.addIceCandidate(event.candidate).then(onAddIceCandidateSuccess, onAddIceCandidateError);
58 }
59
60 function iceCallback2(event, filterOutICECandidate)
61 {
62     if (filterOutICECandidate && filterOutICECandidate(event.candidate))
63         return;
64
65     localConnection.addIceCandidate(event.candidate).then(onAddIceCandidateSuccess, onAddIceCandidateError);
66 }
67
68 function onAddIceCandidateSuccess()
69 {
70 }
71
72 function onAddIceCandidateError(error)
73 {
74     console.log("addIceCandidate error: " + error)
75     assert_unreached();
76 }
77
78 function analyseAudio(stream, duration, context)
79 {
80     return new Promise((resolve, reject) => {
81         var sourceNode = context.createMediaStreamSource(stream);
82
83         var analyser = context.createAnalyser();
84         var gain = context.createGain();
85
86         var results = { heardHum: false, heardBip: false, heardBop: false };
87
88         analyser.fftSize = 2048;
89         analyser.smoothingTimeConstant = 0;
90         analyser.minDecibels = -100;
91         analyser.maxDecibels = 0;
92         gain.gain.value = 0;
93
94         sourceNode.connect(analyser);
95         analyser.connect(gain);
96         gain.connect(context.destination);
97
98        function analyse() {
99            var freqDomain = new Uint8Array(analyser.frequencyBinCount);
100            analyser.getByteFrequencyData(freqDomain);
101
102            var hasFrequency = expectedFrequency => {
103                 var bin = Math.floor(expectedFrequency * analyser.fftSize / context.sampleRate);
104                 return bin < freqDomain.length && freqDomain[bin] >= 150;
105            };
106
107            if (!results.heardHum)
108                 results.heardHum = hasFrequency(150);
109
110            if (!results.heardBip)
111                results.heardBip = hasFrequency(1500);
112
113            if (!results.heardBop)
114                 results.heardBop = hasFrequency(500);
115
116             if (results.heardHum && results.heardBip && results.heardBop)
117                 done();
118         };
119
120        function done() {
121             clearTimeout(timeout);
122             clearInterval(interval);
123             resolve(results);
124        }
125
126         var timeout = setTimeout(done, 3 * duration);
127         var interval = setInterval(analyse, duration / 30);
128         analyse();
129     });
130 }
131
132 function waitFor(duration)
133 {
134     return new Promise((resolve) => setTimeout(resolve, duration));
135 }
136
137 function waitForVideoSize(video, width, height, count)
138 {
139     if (video.videoWidth === width && video.videoHeight === height)
140         return Promise.resolve("video has expected size");
141
142     if (count === undefined)
143         count = 0;
144     if (++count > 20)
145         return Promise.reject("waitForVideoSize timed out, expected " + width + "x"+ height + " but got " + video.videoWidth + "x" + video.videoHeight);
146
147     return waitFor(100).then(() => {
148         return waitForVideoSize(video, width, height, count);
149     });
150 }
151
152 async function doHumAnalysis(stream, expected)
153 {
154     var context = new webkitAudioContext();
155     for (var cptr = 0; cptr < 20; cptr++) {
156         var results = await analyseAudio(stream, 200, context);
157         if (results.heardHum === expected)
158             return true;
159         await waitFor(50);
160     }
161     await context.close();
162     return false;
163 }
164
165 function isVideoBlack(canvas, video, startX, startY, grabbedWidth, grabbedHeight)
166 {
167     canvas.width = video.videoWidth;
168     canvas.height = video.videoHeight;
169     if (!grabbedHeight) {
170         startX = 0;
171         startY = 0;
172         grabbedWidth = canvas.width;
173         grabbedHeight = canvas.height;
174     }
175
176     canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
177
178     imageData = canvas.getContext('2d').getImageData(startX, startY, grabbedWidth, grabbedHeight);
179     data = imageData.data;
180     for (var cptr = 0; cptr < grabbedWidth * grabbedHeight; ++cptr) {
181         // Approximatively black pixels.
182         if (data[4 * cptr] > 30 || data[4 * cptr + 1] > 30 || data[4 * cptr + 2] > 30)
183             return false;
184     }
185     return true;
186 }
187
188 async function checkVideoBlack(expected, canvas, video, errorMessage, counter)
189 {
190     if (isVideoBlack(canvas, video) === expected)
191         return Promise.resolve();
192
193     if (counter > 50) {
194         if (!errorMessage)
195             errorMessage = "checkVideoBlack timed out expecting " + expected;
196         return Promise.reject(errorMessage);
197     }
198
199     await waitFor(50);
200     return checkVideoBlack(expected, canvas, video, errorMessage, ++counter);
201 }