| <!DOCTYPE html> |
| <html> |
| <head><title>GetUserMedia test</title></head> |
| <body> |
| <script src="third_party/ssim.js"></script> |
| <script src="third_party/blackframe.js"></script> |
| <script> |
| const resolutions = [[640, 480], [1280, 720]]; |
| let isVideoInputFound = false; |
| let scriptReady = false; |
| let isTestDone = false; |
| let enumerateDevicesError = ''; |
| const globalErrors = []; |
| const results = []; |
| const logs = []; |
| |
| function addLog(msg) { |
| const curTime = new Date().toISOString(); |
| logs.push(`[${curTime}] ${msg}`); |
| } |
| |
| function testNextResolution(durationSec) { |
| addLog('testNextResolution'); |
| const nextResolution = resolutions.shift(); |
| if (nextResolution === undefined) { |
| reportTestDone(); |
| return; |
| } |
| const test = new CameraTest(nextResolution, durationSec); |
| test.start(); |
| } |
| |
| function reportTestDone() { |
| console.log('tests completed'); |
| isTestDone = true; |
| } |
| |
| function getResults() { |
| return results; |
| } |
| |
| function getLogs() { |
| return logs; |
| } |
| |
| function resolutionMatchesIndependentOfRotation(aWidth, aHeight, |
| bWidth, bHeight) { |
| return (aWidth === bWidth && aHeight === bHeight) || |
| (aWidth === bHeight && aHeight === bWidth); |
| } |
| |
| function saveResult(resolution, verdict) { |
| results.push(verdict); |
| } |
| |
| // Check if a video input exists |
| function checkVideoInput() { |
| addLog('checkVideoInput'); |
| navigator.mediaDevices.enumerateDevices() |
| .then(findVideoInput) |
| .catch(gotEnumerateDevicesError); |
| return isVideoInputFound; |
| } |
| |
| function findVideoInput(devices) { |
| addLog('findVideoInput'); |
| isVideoInputFound = devices.some((dev) => dev.kind == 'videoinput'); |
| } |
| |
| function gotEnumerateDevicesError(error) { |
| console.log('navigator.mediaDevices.enumerateDevices error: ', error); |
| enumerateDevicesError = error.toString(); |
| } |
| |
| function CameraTest(resolution, durationSec) { |
| this.resolution = resolution; |
| this.durationSec = durationSec; |
| this.localVideo = document.createElement('video'); |
| this.localVideo.id = 'local-video'; |
| this.localVideo.autoplay = true; |
| document.body.appendChild(this.localVideo); |
| this.localStream = null; |
| this.canvas = document.createElement('canvas'); |
| this.context = this.canvas.getContext('2d'); |
| this.previousFrame = []; |
| this.identicalFrameSsimThreshold = 0.985; |
| this.frameComparator = new Ssim(); |
| this.results = { |
| width: resolution[0], |
| height: resolution[1], |
| errors: [], |
| frameStats: {totalFrames: 0, blackFrames: 0, frozenFrames: 0}, |
| }; |
| |
| this.constraints = { |
| 'audio': false, |
| 'video': { |
| 'mandatory': { |
| 'maxWidth': this.resolution[0].toString(), |
| 'maxHeight': this.resolution[1].toString(), |
| 'minWidth': this.resolution[0].toString(), |
| 'minHeight': this.resolution[1].toString(), |
| }, |
| }, |
| }; |
| } |
| |
| CameraTest.prototype = { |
| |
| start: function() { |
| addLog(`CameraTest.start: ${this.resolution[0]}x${this.resolution[1]}`); |
| this.localVideo.addEventListener('play', |
| this.startCheckingVideoFrames.bind(this), false); |
| |
| navigator.mediaDevices.getUserMedia(this.constraints) |
| .then(this.gotLocalStream.bind(this)) |
| .catch(this.gotUserMediaError.bind(this)); |
| }, |
| |
| gotLocalStream: function(stream) { |
| addLog('CameraTest.gotLocalStream'); |
| this.localStream = stream; |
| this.localVideo.srcObject = stream; |
| setTimeout(() => { |
| this.stop(); |
| testNextResolution(this.durationSec); |
| }, 1000 * this.durationSec); |
| }, |
| |
| gotUserMediaError: function(error) { |
| console.log('navigator.mediaDevices.getUserMedia error: ', error); |
| this.results.errors.push('GetUserMedia error: ' + error.toString()); |
| }, |
| |
| startCheckingVideoFrames: function() { |
| addLog('CameraTest.startCheckingVideoFrames'); |
| if (!resolutionMatchesIndependentOfRotation(this.localVideo.videoWidth, |
| this.localVideo.videoHeight, this.resolution[0], this.resolution[1])) { |
| this.results.errors.push('resolution', 'Got ' + |
| this.localVideo.videoWidth + 'x' + this.localVideo.videoHeight + |
| ', expected ' + this.resolution[0] + 'x' + this.resolution[1] + |
| ' or rotated version thereof'); |
| } |
| |
| this.videoFrameChecker = setInterval(this.checkVideoFrame.bind(this), 20); |
| }, |
| |
| checkVideoFrame: function() { |
| this.context.drawImage(this.localVideo, 0, 0, this.canvas.width, |
| this.canvas.height); |
| const imageData = this.context.getImageData(0, 0, this.canvas.width, |
| this.canvas.height); |
| |
| if (isBlackFrame(imageData.data, imageData.data.length)) { |
| this.results.frameStats.blackFrames++; |
| } |
| |
| if (this.frameComparator.calculate(this.previousFrame, imageData.data) > |
| this.identicalFrameSsimThreshold) { |
| this.results.frameStats.frozenFrames++; |
| } |
| |
| this.previousFrame = imageData.data; |
| this.results.frameStats.totalFrames++; |
| }, |
| |
| stop: function() { |
| addLog('CameraTest.start'); |
| clearInterval(this.videoFrameChecker); |
| saveResult(this.resolution, this.results); |
| this.localStream.getTracks().forEach((track) => track.stop()); |
| this.localVideo.srcObject = null; |
| document.body.removeChild(this.localVideo); |
| }, |
| }; |
| |
| window.onerror = function(message, filename, lineno, colno, error) { |
| const msg = 'Something went wrong, here is the stack trace --> ' + |
| error.stack; |
| console.log(msg); |
| globalErrors.push(msg); |
| }; |
| |
| scriptReady = true; |
| </script> |
| </body> |
| </html> |