-
Notifications
You must be signed in to change notification settings - Fork 0
/
webcam_injector.js
311 lines (210 loc) · 9.11 KB
/
webcam_injector.js
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
var _isWebAssemblySupported = (function() {
function testSafariWebAssemblyBug() {
var bin = new Uint8Array([0,97,115,109,1,0,0,0,1,6,1,96,1,127,1,127,3,2,1,0,5,3,1,0,1,7,8,1,4,116,101,115,116,0,0,10,16,1,14,0,32,0,65,1,54,2,0,32,0,40,2,0,11]);
var mod = new WebAssembly.Module(bin);
var inst = new WebAssembly.Instance(mod, {});
// test storing to and loading from a non-zero location via a parameter.
// Safari on iOS 11.2.5 returns 0 unexpectedly at non-zero locations
return (inst.exports.test(4) !== 0);
}
var isWebAssemblySupported = (typeof WebAssembly === 'object');
if(isWebAssemblySupported && !testSafariWebAssemblyBug()) {
isWebAssemblySupported = false;
}
return isWebAssemblySupported;
})();
function readWASMBinary(url, onload, onerror, onprogress) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "arraybuffer";
xhr.onload = function xhr_onload() {
if (xhr.status === 200 || xhr.status === 0 && xhr.response) {
onload(xhr.response);
return;
}
onerror()
};
xhr.onerror = onerror;
xhr.onprogress = onprogress;
xhr.send(null);
}
function addBRFScript() {
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("async", true);
script.setAttribute("src", brfv4BaseURL + brfv4SDKName + ".js");
document.getElementsByTagName("head")[0].appendChild(script);
}
// Some necessary global vars... (will need to refactor Stats for BRFv5.)
var brfv4Example = { stats: {} };
var brfv4BaseURL = _isWebAssemblySupported ? "https://cdn.jsdelivr.net/gh/Tastenkunst/brfv4_javascript_examples/js/libs/brf_wasm/" :
"https://cdn.jsdelivr.net/gh/Tastenkunst/brfv4_javascript_examples/js/libs/brf_asmjs/";
var brfv4SDKName = "BRFv4_JS_TK210219_v4.2.0_trial"; // the currently available library
var brfv4WASMBuffer = null;
var u = document.getElementById("_lr_upthresh");
var s = document.getElementById("_lr_speed");
var d = document.getElementById("_lr_dthresh");
var upthresh = parseFloat(u.innerHTML);
var downthresh = parseFloat(d.innerHTML);
var speed = parseInt(s.innerHTML);
var handleTrackingResults = function(brfv4, faces, imageDataCtx) {
for(var i = 0; i < faces.length; i++) {
var face = faces[i];
if(face.state === brfv4.BRFState.FACE_TRACKING_START ||
face.state === brfv4.BRFState.FACE_TRACKING) {
imageDataCtx.strokeStyle="#00a0ff";
for(var k = 0; k < face.vertices.length; k += 2) {
imageDataCtx.beginPath();
imageDataCtx.arc(face.vertices[k], face.vertices[k + 1], 2, 0, 2 * Math.PI);
imageDataCtx.stroke();
}
}
if(face.rotationX > downthresh){
window.scrollBy(0, speed);
}
if(face.rotationX < upthresh){
window.scrollBy(0, -1 * speed);
}
}
};
var onResize = function() {
// implement this function in your minimal example, eg. fill the whole browser.
};
var onInitBRFv4 = function(brfManager, resolution) {
// Will be called when BRFv4 was initialized.
// implement this function in your minimal example.
};
function initExample() {
// This function is called after the BRFv4 script was added.
// BRFv4 needs the correct input image data size for initialization.
// That's why we need to start the camera stream first and get the correct
// video stream dimension. (startCamera, onStreamFetched, onStreamDimensionsAvailable)
// Once the dimension of the video stream is known we need to wait for
// BRFv4 to be ready to be initialized (waitForSDK, initSDK)
// Once BRFv4 was initialized, we can track faces (trackFaces)
var webcam = document.getElementById("_webcam"); // our webcam video
var imageData = document.getElementById("_imageData"); // image data for BRFv4
var imageDataCtx = null; // only fetch the context once
var brfv4 = null; // the library namespace
var brfManager = null; // the API
var resolution = null; // the video stream resolution (usually 640x480)
var timeoutId = -1;
// iOS has this weird behavior that it freezes the camera stream, if the CPU get's
// stressed too much, but it doesn't unfreeze the stream upon CPU relaxation.
// A workaround is to get the video stream dimension and then turn the stream off
// until BRFv4 was successfully initialized (takes about 3 seconds of heavy CPU work).
var isIOS = (/iPad|iPhone|iPod/.test(window.navigator.userAgent) && !window.MSStream);
startCamera();
function startCamera() {
console.log("startCamera");
// Start video playback once the camera was fetched to get the actual stream dimension.
function onStreamFetched (mediaStream) {
console.log("onStreamFetched");
webcam.srcObject = mediaStream;
webcam.play();
// Check whether we know the stream dimension yet, if so, start BRFv4.
function onStreamDimensionsAvailable () {
console.log("onStreamDimensionsAvailable: " + (webcam.videoWidth !== 0));
if(webcam.videoWidth === 0) {
setTimeout(onStreamDimensionsAvailable, 100);
} else {
// Resize the canvas to match the webcam video size.
imageData.width = webcam.videoWidth;
imageData.height = webcam.videoHeight;
imageDataCtx = imageData.getContext("2d");
window.addEventListener("resize", onResize);
onResize();
// on iOS we want to close the video stream first and
// wait for the heavy BRFv4 initialization to finish.
// Once that is done, we start the stream again.
// as discussed above, close the stream on iOS and wait for BRFv4 to be initialized.
if(isIOS) {
webcam.pause();
webcam.srcObject.getTracks().forEach(function(track) {
track.stop();
});
}
waitForSDK();
}
}
// imageDataCtx is not null if we restart the camera stream on iOS.
if(imageDataCtx === null) {
onStreamDimensionsAvailable();
} else {
trackFaces();
}
}
// start the camera stream...
window.navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480, frameRate: 30 } })
.then(onStreamFetched).catch(function (e) { console.log(e); alert("No camera available."); });
}
function waitForSDK() {
if(brfv4 === null && window.hasOwnProperty("initializeBRF")) {
// Set up the namespace and initialize BRFv4.
// locateFile tells the asm.js version where to find the .mem file.
// wasmBinary gets the preloaded .wasm file.
brfv4 = {
locateFile: function(fileName) { return brfv4BaseURL + fileName; },
wasmBinary: brfv4WASMBuffer // Add loaded WASM file to Module
};
initializeBRF(brfv4);
}
if(brfv4 && brfv4.sdkReady) {
initSDK();
} else {
setTimeout(waitForSDK, 250); // wait a bit...
}
}
function initSDK() {
// The brfv4 namespace is now filled with the API classes and objects.
// We can now initialize the BRFManager and the tracking API.
resolution = new brfv4.Rectangle(0, 0, imageData.width, imageData.height);
brfManager = new brfv4.BRFManager();
brfManager.init(resolution, resolution, "com.tastenkunst.brfv4.js.examples.minimal.webcam");
onInitBRFv4(brfManager, resolution);
if(isIOS) {
// Start the camera stream again on iOS.
setTimeout(function () {
console.log('delayed camera restart for iOS');
startCamera();
}, 2000)
} else {
trackFaces();
}
}
function trackFaces() {
if(brfv4Example.stats.start) brfv4Example.stats.start();
var timeStart = window.performance.now();
imageDataCtx.setTransform(-1.0, 0, 0, 1, resolution.width, 0); // A virtual mirror should be... mirrored
imageDataCtx.drawImage(webcam, 0, 0, resolution.width, resolution.height);
imageDataCtx.setTransform( 1.0, 0, 0, 1, 0, 0); // unmirrored for drawing the results
brfManager.update(imageDataCtx.getImageData(0, 0, resolution.width, resolution.height).data);
handleTrackingResults(brfv4, brfManager.getFaces(), imageDataCtx);
if(brfv4Example.stats.end) brfv4Example.stats.end();
if(timeoutId >= 0) {
clearTimeout(timeoutId);
}
var elapstedMs = window.performance.now() - timeStart;
// We don't need 60 FPS, the camera will deliver at 30 FPS anyway.
timeoutId = setTimeout(function() { trackFaces(); }, (1000 / 30) - elapstedMs);
}
}
(function() {
// detect WebAssembly support and load either WASM or ASM version of BRFv4
console.log("Checking support of WebAssembly: " +
_isWebAssemblySupported + " " + (_isWebAssemblySupported ? "loading WASM (not ASM)." : "loading ASM (not WASM)."));
if(_isWebAssemblySupported) {
readWASMBinary(brfv4BaseURL + brfv4SDKName + ".wasm",
function(r) {
brfv4WASMBuffer = r; // see function waitForSDK. The ArrayBuffer needs to be added to the module object.
addBRFScript();
initExample();
},
function (e) { console.error(e); },
function (p) { console.log(p); }
);
} else {
addBRFScript();
initExample();
}
})();