diff --git a/.eslintrc b/.eslintrc index 881e9039..3d8418e4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -66,11 +66,13 @@ "arrayMin": true, "TURN_URL": true, "Ssim": true, + "VideoFrameChecker": true, "StatisticsAggregate": true, "API_KEY": true, "doGetUserMedia": true, "Call": true, "setTimeoutWithProgressBar": true, "ga": true, + "module": true, } } diff --git a/index.js b/index.js new file mode 100644 index 00000000..669ebbaa --- /dev/null +++ b/index.js @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* expose VideoFrameChecker and Ssim as modules without exposing the internal + * structure of the source code. + */ +'use strict'; +module.exports = { + Ssim: require('./src/js/ssim'), + VideoFrameChecker: require('./src/js/videoframechecker') +}; diff --git a/src/index.html b/src/index.html index 94802339..57aa409e 100644 --- a/src/index.html +++ b/src/index.html @@ -51,6 +51,7 @@ + diff --git a/src/js/camresolutionstest.js b/src/js/camresolutionstest.js index 6cbe6f62..f6395be9 100644 --- a/src/js/camresolutionstest.js +++ b/src/js/camresolutionstest.js @@ -289,79 +289,3 @@ CamResolutionsTest.prototype = { } }; -// TODO: Move this to a separate file. -function VideoFrameChecker(videoElement) { - this.frameStats = { - numFrozenFrames: 0, - numBlackFrames: 0, - numFrames: 0 - }; - - this.running_ = true; - - this.nonBlackPixelLumaThreshold = 20; - this.previousFrame_ = []; - this.identicalFrameSsimThreshold = 0.985; - this.frameComparator = new Ssim(); - - this.canvas_ = document.createElement('canvas'); - this.videoElement_ = videoElement; - this.listener_ = this.checkVideoFrame_.bind(this); - this.videoElement_.addEventListener('play', this.listener_, false); -} - -VideoFrameChecker.prototype = { - stop: function() { - this.videoElement_.removeEventListener('play' , this.listener_); - this.running_ = false; - }, - - getCurrentImageData_: function() { - this.canvas_.width = this.videoElement_.width; - this.canvas_.height = this.videoElement_.height; - - var context = this.canvas_.getContext('2d'); - context.drawImage(this.videoElement_, 0, 0, this.canvas_.width, - this.canvas_.height); - return context.getImageData(0, 0, this.canvas_.width, this.canvas_.height); - }, - - checkVideoFrame_: function() { - if (!this.running_) { - return; - } - if (this.videoElement_.ended) { - return; - } - - var imageData = this.getCurrentImageData_(); - - if (this.isBlackFrame_(imageData.data, imageData.data.length)) { - this.frameStats.numBlackFrames++; - } - - if (this.frameComparator.calculate(this.previousFrame_, imageData.data) > - this.identicalFrameSsimThreshold) { - this.frameStats.numFrozenFrames++; - } - this.previousFrame_ = imageData.data; - - this.frameStats.numFrames++; - setTimeout(this.checkVideoFrame_.bind(this), 20); - }, - - isBlackFrame_: function(data, length) { - // TODO: Use a statistical, histogram-based detection. - var thresh = this.nonBlackPixelLumaThreshold; - var accuLuma = 0; - for (var i = 4; i < length; i += 4) { - // Use Luma as in Rec. 709: Y′709 = 0.21R + 0.72G + 0.07B; - accuLuma += 0.21 * data[i] + 0.72 * data[i + 1] + 0.07 * data[i + 2]; - // Early termination if the average Luma so far is bright enough. - if (accuLuma > (thresh * i / 4)) { - return false; - } - } - return true; - } -}; diff --git a/src/js/ssim.js b/src/js/ssim.js index 6ee33d4b..34c1291d 100644 --- a/src/js/ssim.js +++ b/src/js/ssim.js @@ -87,3 +87,7 @@ Ssim.prototype = { return luminance * contrast * structure; } }; + +if (typeof exports === 'object') { + module.exports = Ssim; +} diff --git a/src/js/videoframechecker.js b/src/js/videoframechecker.js new file mode 100644 index 00000000..1756e4aa --- /dev/null +++ b/src/js/videoframechecker.js @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + +/* More information about these options at jshint.com/docs/options */ +'use strict'; +function VideoFrameChecker(videoElement) { + this.frameStats = { + numFrozenFrames: 0, + numBlackFrames: 0, + numFrames: 0 + }; + + this.running_ = true; + + this.nonBlackPixelLumaThreshold = 20; + this.previousFrame_ = []; + this.identicalFrameSsimThreshold = 0.985; + this.frameComparator = new Ssim(); + + this.canvas_ = document.createElement('canvas'); + this.videoElement_ = videoElement; + this.listener_ = this.checkVideoFrame_.bind(this); + this.videoElement_.addEventListener('play', this.listener_, false); +} + +VideoFrameChecker.prototype = { + stop: function() { + this.videoElement_.removeEventListener('play' , this.listener_); + this.running_ = false; + }, + + getCurrentImageData_: function() { + this.canvas_.width = this.videoElement_.width; + this.canvas_.height = this.videoElement_.height; + + var context = this.canvas_.getContext('2d'); + context.drawImage(this.videoElement_, 0, 0, this.canvas_.width, + this.canvas_.height); + return context.getImageData(0, 0, this.canvas_.width, this.canvas_.height); + }, + + checkVideoFrame_: function() { + if (!this.running_) { + return; + } + if (this.videoElement_.ended) { + return; + } + + var imageData = this.getCurrentImageData_(); + + if (this.isBlackFrame_(imageData.data, imageData.data.length)) { + this.frameStats.numBlackFrames++; + } + + if (this.frameComparator.calculate(this.previousFrame_, imageData.data) > + this.identicalFrameSsimThreshold) { + this.frameStats.numFrozenFrames++; + } + this.previousFrame_ = imageData.data; + + this.frameStats.numFrames++; + setTimeout(this.checkVideoFrame_.bind(this), 20); + }, + + isBlackFrame_: function(data, length) { + // TODO: Use a statistical, histogram-based detection. + var thresh = this.nonBlackPixelLumaThreshold; + var accuLuma = 0; + for (var i = 4; i < length; i += 4) { + // Use Luma as in Rec. 709: Y′709 = 0.21R + 0.72G + 0.07B; + accuLuma += 0.21 * data[i] + 0.72 * data[i + 1] + 0.07 * data[i + 2]; + // Early termination if the average Luma so far is bright enough. + if (accuLuma > (thresh * i / 4)) { + return false; + } + } + return true; + } +}; + +if (typeof exports === 'object') { + module.exports = VideoFrameChecker; +}