2019-08-28 18:28:22 +02:00
2018-12-31 01:03:07 +01:00
import Debug from '../../conf/Debug' ;
import EdgeDetect from './edge-detect/EdgeDetect' ;
import EdgeStatus from './edge-detect/enums/EdgeStatusEnum' ;
import EdgeDetectPrimaryDirection from './edge-detect/enums/EdgeDetectPrimaryDirectionEnum' ;
import EdgeDetectQuality from './edge-detect/enums/EdgeDetectQualityEnum' ;
import GuardLine from './GuardLine' ;
2020-03-08 18:47:01 +01:00
// import DebugCanvas from './DebugCanvas';
2019-02-15 00:00:22 +01:00
import VideoAlignment from '../../../common/enums/video-alignment.enum' ;
2019-03-10 23:27:50 +01:00
import AspectRatio from '../../../common/enums/aspect-ratio.enum' ;
2020-04-28 03:04:15 +02:00
import { sleep } from '../../lib/Util' ;
2018-12-31 01:03:07 +01:00
2018-05-08 23:35:16 +02:00
class ArDetector {
2018-05-09 00:34:22 +02:00
2019-09-03 22:42:38 +02:00
constructor ( videoData ) {
this . logger = videoData . logger ;
2018-05-14 20:39:15 +02:00
this . conf = videoData ;
2018-05-08 23:35:16 +02:00
this . video = videoData . video ;
2018-08-05 23:48:56 +02:00
this . settings = videoData . settings ;
2018-05-08 23:35:16 +02:00
this . setupTimer = null ;
2018-05-14 20:39:15 +02:00
this . sampleCols = [ ] ;
2018-05-09 00:34:22 +02:00
this . canFallback = true ;
this . fallbackMode = false ;
2018-05-12 02:51:58 +02:00
2019-02-15 00:00:22 +01:00
this . blackLevel = this . settings . active . arDetect . blackbar . blackLevel ;
2018-09-23 19:46:40 +02:00
this . arid = ( Math . random ( ) * 100 ) . toFixed ( ) ;
2019-02-15 00:00:22 +01:00
// ar detector starts in this state. running main() sets both to false
this . _halted = true ;
this . _exited = true ;
2019-02-16 01:19:29 +01:00
// we can tick manually, for debugging
this . _manualTicks = false ;
this . _nextTick = false ;
2019-02-15 00:00:22 +01:00
this . canDoFallbackMode = false ;
2020-12-07 00:09:08 +01:00
this . drmNotificationShown = false ;
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'init' , ` [ArDetector::ctor] creating new ArDetector. arid: ${ this . arid } ` ) ;
2018-05-09 00:34:22 +02:00
}
2018-05-08 23:35:16 +02:00
2019-02-16 01:19:29 +01:00
setManualTick ( manualTick ) {
this . _manualTicks = manualTick ;
}
tick ( ) {
this . _nextTick = true ;
}
2018-05-08 23:35:16 +02:00
init ( ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'init' , ` [ArDetect::init] <@ ${ this . arid } > Initializing autodetection. ` ) ;
2019-02-15 00:00:22 +01:00
try {
2019-02-16 01:19:29 +01:00
if ( this . settings . canStartAutoAr ( ) ) {
this . setup ( ) ;
} else {
throw "Settings prevent autoar from starting"
}
2019-02-15 00:00:22 +01:00
} catch ( e ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'error' , 'init' , ` %c[ArDetect::init] <@ ${ this . arid } > Initialization failed. ` , _ard _console _stop , e ) ;
2019-02-15 00:00:22 +01:00
}
2018-05-08 23:35:16 +02:00
}
2018-05-16 23:26:47 +02:00
destroy ( ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'init' , ` %c[ArDetect::destroy] <@ ${ this . arid } > Destroying aard. ` , _ard _console _stop , e ) ;
2018-12-31 01:03:07 +01:00
// this.debugCanvas.destroy();
2018-09-23 19:46:40 +02:00
this . stop ( ) ;
2018-05-16 23:26:47 +02:00
}
2019-02-16 01:54:41 +01:00
setup ( cwidth , cheight ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'init' , ` [ArDetect::setup] <@ ${ this . arid } > Starting autodetection setup. ` ) ;
2019-02-15 00:00:22 +01:00
//
// [-1] check for zero-width and zero-height videos. If we detect this, we kick the proverbial
// can some distance down the road. This problem will prolly fix itself soon. We'll also
// not do any other setup until this issue is fixed
//
if ( this . video . videoWidth === 0 || this . video . videoHeight === 0 ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'warn' , 'debug' , ` [ArDetect::setup] <@ ${ this . arid } > This video has zero width or zero height. Dimensions: ${ this . video . videoWidth } × ${ this . video . videoHeight } ` ) ;
2019-02-15 00:00:22 +01:00
this . scheduleInitRestart ( ) ;
return ;
2018-07-11 00:01:44 +02:00
}
2019-02-15 00:00:22 +01:00
//
// [0] initiate "dependencies" first
//
this . guardLine = new GuardLine ( this ) ;
this . edgeDetector = new EdgeDetect ( this ) ;
// this.debugCanvas = new DebugCanvas(this);
2018-05-14 20:39:15 +02:00
2019-02-15 00:00:22 +01:00
//
// [1] initiate canvases
//
2018-05-09 00:03:22 +02:00
2019-02-15 00:00:22 +01:00
if ( ! cwidth ) {
cwidth = this . settings . active . arDetect . canvasDimensions . sampleCanvas . width ;
cheight = this . settings . active . arDetect . canvasDimensions . sampleCanvas . height ;
}
2018-05-09 00:03:22 +02:00
2019-02-15 00:00:22 +01:00
if ( this . canvas ) {
this . canvas . remove ( ) ;
}
if ( this . blackframeCanvas ) {
this . blackframeCanvas . remove ( ) ;
}
2018-05-20 23:17:09 +02:00
2019-02-15 00:00:22 +01:00
// things to note: we'll be keeping canvas in memory only.
this . canvas = document . createElement ( "canvas" ) ;
this . canvas . width = cwidth ;
this . canvas . height = cheight ;
this . blackframeCanvas = document . createElement ( "canvas" ) ;
this . blackframeCanvas . width = this . settings . active . arDetect . canvasDimensions . blackframeCanvas . width ;
this . blackframeCanvas . height = this . settings . active . arDetect . canvasDimensions . blackframeCanvas . height ;
2018-05-09 00:03:22 +02:00
2019-02-15 00:00:22 +01:00
this . context = this . canvas . getContext ( "2d" ) ;
this . blackframeContext = this . blackframeCanvas . getContext ( "2d" ) ;
// do setup once
// tho we could do it for every frame
this . canvasScaleFactor = cheight / this . video . videoHeight ;
//
// [2] determine places we'll use to sample our main frame
//
var ncol = this . settings . active . arDetect . sampling . staticCols ;
var nrow = this . settings . active . arDetect . sampling . staticRows ;
2018-05-09 00:03:22 +02:00
2019-02-15 00:00:22 +01:00
var colSpacing = this . canvas . width / ncol ;
var rowSpacing = ( this . canvas . height << 2 ) / nrow ;
this . sampleLines = [ ] ;
this . sampleCols = [ ] ;
2018-05-15 20:36:22 +02:00
2019-02-15 00:00:22 +01:00
for ( var i = 0 ; i < ncol ; i ++ ) {
if ( i < ncol - 1 )
this . sampleCols . push ( Math . round ( colSpacing * i ) ) ;
else {
this . sampleCols . push ( Math . round ( colSpacing * i ) - 1 ) ;
2018-05-09 00:03:22 +02:00
}
2019-02-15 00:00:22 +01:00
}
for ( var i = 0 ; i < nrow ; i ++ ) {
if ( i < ncol - 5 )
this . sampleLines . push ( Math . round ( rowSpacing * i ) ) ;
else {
this . sampleLines . push ( Math . round ( rowSpacing * i ) - 4 ) ;
2018-05-09 00:03:22 +02:00
}
2019-02-15 00:00:22 +01:00
}
2018-05-08 23:35:16 +02:00
2019-02-15 00:00:22 +01:00
//
// [3] detect if we're in the fallback mode and reset guardline
//
if ( this . fallbackMode ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'warn' , 'debug' , ` [ArDetect::setup] <@ ${ this . arid } > WARNING: CANVAS RESET DETECTED/we're in fallback mode - recalculating guardLine ` , "background: #000; color: #ff2" ) ;
2019-02-15 00:00:22 +01:00
// blackbar, imagebar
this . guardLine . reset ( ) ;
2018-05-09 00:03:22 +02:00
}
2019-02-15 00:00:22 +01:00
//
// [4] see if browser supports "fallback mode" by drawing a small portion of our window
//
try {
this . blackframeContext . drawWindow ( window , 0 , 0 , this . blackframeCanvas . width , this . blackframeCanvas . height , "rgba(0,0,128,1)" ) ;
this . canDoFallbackMode = true ;
} catch ( e ) {
this . canDoFallbackMode = false ;
}
//
// [5] do other things setup needs to do
//
this . detectionTimeoutEventCount = 0 ;
this . resetBlackLevel ( ) ;
// if we're restarting ArDetect, we need to do this in order to force-recalculate aspect ratio
2019-07-04 22:46:18 +02:00
this . conf . resizer . setLastAr ( { type : AspectRatio . Automatic , ratio : this . getDefaultAr ( ) } ) ;
2019-02-15 00:00:22 +01:00
this . canvasImageDataRowLength = cwidth << 2 ;
this . noLetterboxCanvasReset = false ;
2019-02-16 01:19:29 +01:00
if ( this . settings . canStartAutoAr ( ) ) {
2019-02-15 00:00:22 +01:00
this . start ( ) ;
2018-05-09 00:03:22 +02:00
}
if ( Debug . debugCanvas . enabled ) {
2018-12-31 01:03:07 +01:00
// this.debugCanvas.init({width: cwidth, height: cheight});
2018-05-09 00:03:22 +02:00
// DebugCanvas.draw("test marker","test","rect", {x:5, y:5}, {width: 5, height: 5});
}
2018-05-16 23:26:47 +02:00
this . conf . arSetupComplete = true ;
2018-05-08 23:35:16 +02:00
}
2019-02-16 01:19:29 +01:00
start ( ) {
2020-06-04 22:44:53 +02:00
if ( this . settings . canStartAutoAr ( ) ) {
this . logger . log ( 'info' , 'debug' , ` "%c[ArDetect::start] <@ ${ this . arid } > Starting automatic aspect ratio detection ` , _ard _console _start ) ;
} else {
this . logger . log ( 'warn' , 'debug' , ` "%c[ArDetect::start] <@ ${ this . arid } > Wanted to start automatic aspect ratio detection, but settings don't allow that. Aard won't be started. ` , _ard _console _change ) ;
return ;
}
2019-07-16 22:46:16 +02:00
2019-03-10 23:27:50 +01:00
if ( this . conf . resizer . lastAr . type === AspectRatio . Automatic ) {
2019-02-16 01:19:29 +01:00
// ensure first autodetection will run in any case
2019-07-04 22:46:18 +02:00
this . conf . resizer . setLastAr ( { type : AspectRatio . Automatic , ratio : this . getDefaultAr ( ) } ) ;
2019-02-16 01:19:29 +01:00
}
2019-02-15 00:00:22 +01:00
2019-08-28 18:28:22 +02:00
2019-02-15 00:00:22 +01:00
// launch main() if it's currently not running:
this . main ( ) ;
2019-08-28 18:28:22 +02:00
// automatic detection starts halted. If halted=false when main first starts, extension won't run
// this._paused is undefined the first time we run this function, which is effectively the same thing
// as false. Still, we'll explicitly fix this here.
this . _paused = false ;
2019-02-15 00:00:22 +01:00
this . _halted = false ;
this . _paused = false ;
2018-05-08 23:35:16 +02:00
}
2018-07-11 23:13:40 +02:00
unpause ( ) {
2019-08-28 18:28:22 +02:00
// pause only if we were running before. Don't pause if we aren't running
// (we are running when _halted is neither true nor undefined)
if ( this . _paused && this . _halted === false ) {
this . _paused = true ;
2018-07-11 23:13:40 +02:00
}
}
pause ( ) {
// pause only if we were running before. Don't pause if we aren't running
// (we are running when _halted is neither true nor undefined)
if ( this . _halted === false ) {
this . _paused = true ;
}
}
2018-05-08 23:35:16 +02:00
stop ( ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'debug' , ` "%c[ArDetect::stop] <@ ${ this . arid } > Stopping automatic aspect ratio detection ` , _ard _console _stop ) ;
2018-05-08 23:35:16 +02:00
this . _halted = true ;
2019-07-04 22:46:18 +02:00
// this.conf.resizer.setArLastAr();
2018-05-08 23:35:16 +02:00
}
2019-02-15 00:00:22 +01:00
async main ( ) {
2019-08-28 18:28:22 +02:00
if ( this . _paused ) {
2019-02-15 00:00:22 +01:00
// unpause if paused
this . _paused = false ;
2019-08-28 18:28:22 +02:00
return ; // main loop still keeps executing. Return is needed to avoid a million instances of autodetection
2019-02-15 00:00:22 +01:00
}
if ( ! this . _halted ) {
// we are already running, don't run twice
2019-08-28 18:28:22 +02:00
// this would have handled the 'paused' from before, actually.
2019-02-15 00:00:22 +01:00
return ;
}
2019-02-15 20:40:56 +01:00
let exitedRetries = 10 ;
2019-02-15 00:00:22 +01:00
while ( ! this . _exited && exitedRetries -- > 0 ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'warn' , 'debug' , ` [ArDetect::main] <@ ${ this . arid } > We are trying to start another instance of autodetection on current video, but the previous instance hasn't exited yet. Waiting for old instance to exit ... ` ) ;
2020-04-28 03:04:15 +02:00
await sleep ( this . settings . active . arDetect . timers . tickrate ) ;
2019-02-15 00:00:22 +01:00
}
if ( ! this . _exited ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'error' , 'debug' , ` [ArDetect::main] <@ ${ this . arid } > Previous instance didn't exit in time. Not starting a new one. ` ) ;
2019-02-15 00:00:22 +01:00
return ;
}
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'debug' , ` %c[ArDetect::main] <@ ${ this . arid } > Previous instance didn't exit in time. Not starting a new one. ` ) ;
2019-02-15 00:00:22 +01:00
// we need to unhalt:
this . _halted = false ;
this . _exited = false ;
// set initial timestamps so frame check will trigger the first time we run the loop
let lastFrameCheckStartTime = Date . now ( ) - ( this . settings . active . arDetect . timers . playing << 1 ) ;
2019-02-16 01:19:29 +01:00
const frameCheckTimes = new Array ( 10 ) . fill ( - 1 ) ;
let frameCheckBufferIndex = 0 ;
let fcstart , fctime ;
2019-02-15 00:00:22 +01:00
while ( this && ! this . _halted ) {
// NOTE: we separated tickrate and inter-check timeouts so that when video switches
// state from 'paused' to 'playing', we don't need to wait for the rest of the longer
// paused state timeout to finish.
2019-02-16 01:19:29 +01:00
if ( ( ! this . _manualTicks && this . canTriggerFrameCheck ( lastFrameCheckStartTime ) ) || this . _nextTick ) {
this . _nextTick = false ;
2019-02-15 00:00:22 +01:00
2019-02-16 01:19:29 +01:00
lastFrameCheckStartTime = Date . now ( ) ;
fcstart = performance . now ( ) ;
2019-05-05 00:09:49 +02:00
try {
this . frameCheck ( ) ;
} catch ( e ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'error' , 'debug' , ` %c[ArDetect::main] <@ ${ this . arid } > Frame check failed: ` , "color: #000, background: #f00" , e ) ;
2019-05-05 00:09:49 +02:00
}
2019-02-16 01:19:29 +01:00
if ( Debug . performanceMetrics ) {
fctime = performance . now ( ) - fcstart ;
frameCheckTimes [ frameCheckBufferIndex % frameCheckTimes . length ] = fctime ;
this . conf . pageInfo . sendPerformanceUpdate ( { frameCheckTimes : frameCheckTimes , lastFrameCheckTime : fctime } ) ;
++ frameCheckBufferIndex ;
}
2019-02-15 00:00:22 +01:00
}
2020-04-28 03:04:15 +02:00
await sleep ( this . settings . active . arDetect . timers . tickrate ) ;
2019-02-15 00:00:22 +01:00
}
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'debug' , ` %c[ArDetect::main] <@ ${ this . arid } > Main autodetection loop exited. Halted? ${ this . _halted } ` , _ard _console _stop ) ;
2019-02-15 00:00:22 +01:00
this . _exited = true ;
}
canTriggerFrameCheck ( lastFrameCheckStartTime ) {
if ( this . _paused ) {
return false ;
}
if ( this . video . ended || this . video . paused ) {
// we slow down if ended or pausing. Detecting is pointless.
// we don't stop outright in case seeking happens during pause/after video was
// ended and video gets into 'playing' state again
return Date . now ( ) - lastFrameCheckStartTime > this . settings . active . arDetect . timers . paused ;
}
if ( this . video . error ) {
// če je video pavziran, še vedno skušamo zaznati razmerje stranic - ampak bolj poredko.
// if the video is paused, we still do autodetection. We just do it less often.
return Date . now ( ) - lastFrameCheckStartTime > this . settings . active . arDetect . timers . error ;
}
return Date . now ( ) - lastFrameCheckStartTime > this . settings . active . arDetect . timers . playing ;
}
2018-05-12 01:51:43 +02:00
isRunning ( ) {
2019-02-15 00:00:22 +01:00
return ! ( this . _halted || this . _paused || this . _exited ) ;
2018-05-12 01:51:43 +02:00
}
2018-07-11 23:13:40 +02:00
2018-05-20 23:17:09 +02:00
scheduleInitRestart ( timeout , force _reset ) {
if ( ! timeout ) {
timeout = 100 ;
}
// don't allow more than 1 instance
if ( this . setupTimer ) {
clearTimeout ( this . setupTimer ) ;
}
var ths = this ;
this . setupTimer = setTimeout ( function ( ) {
ths . setupTimer = null ;
try {
2019-07-16 22:46:16 +02:00
ths . main ( ) ;
} catch ( e ) {
this . logger ( 'error' , 'debug' , ` [ArDetector::scheduleInitRestart] <@ ${ this . arid } > Failed to start main(). Error: ` , e ) ;
}
2018-05-20 23:17:09 +02:00
ths = null ;
} ,
timeout
) ;
}
2018-05-15 20:36:22 +02:00
2018-05-12 01:51:43 +02:00
2018-05-09 00:34:22 +02:00
//#region helper functions (general)
attachCanvas ( canvas ) {
if ( this . attachedCanvas )
this . attachedCanvas . remove ( ) ;
// todo: place canvas on top of the video instead of random location
canvas . style . position = "absolute" ;
canvas . style . left = "200px" ;
canvas . style . top = "1200px" ;
canvas . style . zIndex = 10000 ;
document . getElementsByTagName ( "body" ) [ 0 ]
. appendChild ( canvas ) ;
}
canvasReadyForDrawWindow ( ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'debug' , ` %c[ArDetect::canvasReadyForDrawWindow] <@ ${ this . arid } > canvas is ${ this . canvas . height === window . innerHeight ? '' : 'NOT ' } ready for drawWindow(). Canvas height: ${ this . canvas . height } px; window inner height: ${ window . innerHeight } px. ` )
2018-05-09 00:34:22 +02:00
return this . canvas . height == window . innerHeight
}
2018-05-09 00:58:50 +02:00
getTimeout ( baseTimeout , startTime ) {
var execTime = ( performance . now ( ) - startTime ) ;
return baseTimeout ;
}
2018-05-09 00:34:22 +02:00
//#endregion
2019-07-04 22:46:18 +02:00
getDefaultAr ( ) {
2020-03-01 16:55:52 +01:00
const ratio = this . video . videoWidth / this . video . videoHeight ;
if ( isNaN ( ratio ) ) {
return undefined ;
}
return ratio ;
2019-07-04 22:46:18 +02:00
}
2018-07-11 00:01:44 +02:00
calculateArFromEdges ( edges ) {
2018-05-09 00:34:22 +02:00
// if we don't specify these things, they'll have some default values.
if ( edges . top === undefined ) {
edges . top = 0 ;
edges . bottom = 0 ;
2019-05-05 00:09:49 +02:00
edges . left = 0 ; // RESERVED FOR FUTURE — CURRENTLY UNUSED
edges . right = 0 ; // THIS FUNCTION CAN PRESENTLY ONLY HANDLE LETTERBOX
2018-05-09 00:34:22 +02:00
}
2018-07-11 00:01:44 +02:00
2019-05-26 02:54:02 +02:00
let letterbox = edges . top + edges . bottom ;
2019-05-05 00:09:49 +02:00
if ( ! this . fallbackMode ) {
// Since video is stretched to fit the canvas, we need to take that into account when calculating target
// aspect ratio and correct our calculations to account for that
const fileAr = this . video . videoWidth / this . video . videoHeight ;
const canvasAr = this . canvas . width / this . canvas . height ;
let widthCorrected ;
if ( edges . top && edges . bottom ) {
// in case of letterbox, we take canvas height as canon and assume width got stretched or squished
if ( fileAr != canvasAr ) {
widthCorrected = this . canvas . height * fileAr ;
} else {
widthCorrected = this . canvas . width ;
}
return widthCorrected / ( this . canvas . height - letterbox ) ;
}
} else {
// fallback mode behaves a wee bit differently
let zoomFactor = 1 ;
2018-07-11 23:13:40 +02:00
// there's stuff missing from the canvas. We need to assume canvas' actual height is bigger by a factor x, where
// x = [video.zoomedHeight] / [video.unzoomedHeight]
//
// letterbox also needs to be corrected:
// letterbox += [video.zoomedHeight] - [video.unzoomedHeight]
var vbr = this . video . getBoundingClientRect ( ) ;
zoomFactor = vbr . height / this . video . clientHeight ;
letterbox += vbr . height - this . video . clientHeight ;
2019-05-05 00:09:49 +02:00
var trueHeight = this . canvas . height * zoomFactor - letterbox ;
2018-07-11 00:01:44 +02:00
2018-08-05 23:48:56 +02:00
if ( edges . top > 1 && edges . top <= this . settings . active . arDetect . fallbackMode . noTriggerZonePx ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect' , ` %c[ArDetect::calculateArFromEdges] <@ ${ this . arid } > Edge is in the no-trigger zone. Aspect ratio change is not triggered. ` )
2018-05-09 00:34:22 +02:00
return ;
2018-05-18 23:26:20 +02:00
}
2018-05-09 00:34:22 +02:00
// varnostno območje, ki naj ostane črno (da lahko v fallback načinu odkrijemo ožanje razmerja stranic).
// x2, ker je safetyBorderPx definiran za eno stran.
// safety border so we can detect aspect ratio narrowing (21:9 -> 16:9).
// x2 because safetyBorderPx is for one side.
2018-08-05 23:48:56 +02:00
trueHeight += ( this . settings . active . arDetect . fallbackMode . safetyBorderPx << 1 ) ;
2018-07-11 00:01:44 +02:00
2019-05-05 00:09:49 +02:00
return this . canvas . width * zoomFactor / trueHeight ;
}
2018-07-11 00:01:44 +02:00
}
processAr ( trueAr ) {
2018-05-09 00:34:22 +02:00
this . detectedAr = trueAr ;
// poglejmo, če se je razmerje stranic spremenilo
// check if aspect ratio is changed:
2018-05-15 21:40:53 +02:00
var lastAr = this . conf . resizer . getLastAr ( ) ;
2020-01-04 02:04:32 +01:00
if ( lastAr . type === AspectRatio . Automatic && lastAr . ratio !== null && lastAr . ratio !== undefined ) {
2018-05-09 00:34:22 +02:00
// spremembo lahko zavrnemo samo, če uporabljamo avtomatski način delovanja in če smo razmerje stranic
// že nastavili.
//
// we can only deny aspect ratio changes if we use automatic mode and if aspect ratio was set from here.
var arDiff = trueAr - lastAr . ar ;
if ( arDiff < 0 )
arDiff = - arDiff ;
var arDiff _percent = arDiff / trueAr ;
// ali je sprememba v mejah dovoljenega? Če da -> fertik
// is ar variance within acceptable levels? If yes -> we done
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect' , ` %c[ArDetect::processAr] <@ ${ this . arid } > New aspect ratio varies from the old one by this much: \n ` , "color: #aaf" , "old Ar" , lastAr . ar , "current ar" , trueAr , "arDiff (absolute):" , arDiff , "ar diff (relative to new ar)" , arDiff _percent ) ;
2018-08-05 23:48:56 +02:00
if ( arDiff < trueAr * this . settings . active . arDetect . allowedArVariance ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect' , ` %c[ArDetect::processAr] <@ ${ this . arid } > Aspect ratio change denied — diff %: ${ arDiff _percent } ` , "background: #740; color: #fa2" ) ;
2018-05-09 00:34:22 +02:00
return ;
}
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect' , ` %c[ArDetect::processAr] <@ ${ this . arid } > aspect ratio change accepted — diff %: ${ arDiff _percent } ` , "background: #153; color: #4f9" ) ;
2019-02-15 20:40:56 +01:00
}
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'debug' , ` %c[ArDetect::processAr] <@ ${ this . arid } > Triggering aspect ratio change. New aspect ratio: ${ trueAr } ` , _ard _console _change ) ;
2018-05-09 00:34:22 +02:00
2019-11-04 23:53:28 +01:00
this . conf . resizer . updateAr ( { type : AspectRatio . Automatic , ratio : trueAr } , { type : AspectRatio . Automatic , ratio : trueAr } ) ;
2018-05-09 00:34:22 +02:00
}
2019-10-29 18:15:46 +01:00
clearImageData ( id ) {
if ( ArrayBuffer . transfer ) {
ArrayBuffer . transfer ( id , 0 ) ;
}
id = undefined ;
}
2020-12-07 00:09:08 +01:00
/ * *
* Checks whether video we ' re trying to play is protected by DRM .
*
* When trying to get an image frame of a DRM - protected video in
* firefox , the method canvas . drawImage ( video ) will throw an exception .
*
* This doesn ' t happen in Chrome . As opposed to Firefox , chrome will
* simply draw a transparent black image and not tell anyone that
* anything is amiss . However , since the image is ( according to my testing
* on netflix ) completely transparent , this means we can determine whether
* the video is DRM - protected by looking at the alpha byte of the image .
*
* ( Videos don 't tend to have an alpha channel, so they' re always
* completely opaque ( i . e . have value of 255 ) )
* /
hasDRM ( ) {
return this . blackframeContext . getImageData ( 0 , 0 , 1 , 1 ) . data [ 3 ] === 0 ;
}
2018-05-09 00:34:22 +02:00
frameCheck ( ) {
2018-05-09 00:58:50 +02:00
if ( ! this . video ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'error' , 'debug' , ` %c[ArDetect::frameCheck] <@ ${ this . arid } > Video went missing. Destroying current instance of videoData. ` ) ;
2018-05-16 23:26:47 +02:00
this . conf . destroy ( ) ;
2018-05-09 00:58:50 +02:00
return ;
}
2019-02-16 01:19:29 +01:00
if ( ! this . blackframeContext ) {
this . init ( ) ;
}
2018-05-09 00:58:50 +02:00
var startTime = performance . now ( ) ;
2019-02-15 00:00:22 +01:00
let sampleCols = this . sampleCols . slice ( 0 ) ;
2017-12-29 23:34:40 +01:00
2019-02-15 00:00:22 +01:00
//
// [0] blackframe tests (they also determine whether we need fallback mode)
//
try {
this . blackframeContext . drawImage ( this . video , 0 , 0 , this . blackframeCanvas . width , this . blackframeCanvas . height ) ;
2020-12-07 00:09:08 +01:00
// special browsers require special tests
if ( this . hasDRM ( ) ) {
2020-12-22 03:32:56 +01:00
this . fallbackMode = false ;
2020-12-07 00:09:08 +01:00
throw 'Video is protected by DRM. Autodetection cannot run here.' ;
}
2019-02-15 00:00:22 +01:00
this . fallbackMode = false ;
} catch ( e ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'error' , 'arDetect' , ` %c[ArDetect::frameCheck] <@ ${ this . arid } > %c[ArDetect::frameCheck] can't draw image on canvas. ${ this . canDoFallbackMode ? 'Trying canvas.drawWindow instead' : 'Doing nothing as browser doesn\'t support fallback mode.' } ` , "color:#000; backgroud:#f51;" , e ) ;
2019-02-15 00:00:22 +01:00
// nothing to see here, really, if fallback mode isn't supported by browser
2020-12-07 00:09:08 +01:00
if ( ! this . drmNotificationShown ) {
this . drmNotificationShown = true ;
this . conf . player . showNotification ( 'AARD_DRM' ) ;
2020-12-22 22:25:41 +01:00
this . conf . resizer . setAr ( { type : AspectRatio . Reset } ) ;
2019-02-15 00:00:22 +01:00
return ;
2020-12-22 03:32:56 +01:00
}
// in case of DRM errors, we need to prevent the execution to reach the aspec
// ratio setting part of this function
if ( e === 'Video is protected by DRM. Autodetection cannot run here.' ) {
return ;
2019-02-15 00:00:22 +01:00
}
2020-12-22 03:32:56 +01:00
2019-02-15 00:00:22 +01:00
if ( ! this . canvasReadyForDrawWindow ( ) ) {
// this means canvas needs to be resized, so we'll just re-run setup with all those new parameters
this . stop ( ) ;
2018-05-09 00:58:50 +02:00
2019-06-12 23:55:15 +02:00
let newCanvasWidth = window . innerHeight * ( this . video . videoWidth / this . video . videoHeight ) ;
let newCanvasHeight = window . innerHeight ;
2019-02-15 00:00:22 +01:00
if ( this . conf . resizer . videoAlignment === VideoAlignment . Center ) {
this . canvasDrawWindowHOffset = Math . round ( ( window . innerWidth - newCanvasWidth ) * 0.5 ) ;
} else if ( this . conf . resizer . videoAlignment === VideoAlignment . Left ) {
this . canvasDrawWindowHOffset = 0 ;
} else {
this . canvasDrawWindowHOffset = window . innerWidth - newCanvasWidth ;
2018-05-09 00:58:50 +02:00
}
2018-09-23 02:39:27 +02:00
2019-02-15 00:00:22 +01:00
this . setup ( newCanvasWidth , newCanvasHeight ) ;
2018-05-09 00:58:50 +02:00
2019-02-15 00:00:22 +01:00
return ;
}
// if this is the case, we'll first draw on canvas, as we'll need intermediate canvas if we want to get a
// smaller sample for blackframe check
this . fallbackMode = true ;
try {
this . context . drawWindow ( window , this . canvasDrawWindowHOffset , 0 , this . canvas . width , this . canvas . height , "rgba(0,0,128,1)" ) ;
} catch ( e ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'error' , 'arDetect' , ` %c[ArDetect::frameCheck] can't draw image on canvas with fallback mode either. This error is prolly only temporary. ` , "color:#000; backgroud:#f51;" , e ) ;
2019-02-15 00:00:22 +01:00
return ; // it's prolly just a fluke, so we do nothing special here
2018-05-09 00:58:50 +02:00
}
2019-02-15 00:00:22 +01:00
// draw blackframe sample from our main sample:
this . blackframeContext . drawImage ( this . canvas , this . blackframeCanvas . width , this . blackframeCanvas . height )
2017-09-27 02:26:47 +02:00
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] canvas.drawImage seems to have worked ` , "color:#000; backgroud:#2f5;" ) ;
2018-05-09 00:58:50 +02:00
}
2017-09-27 02:26:47 +02:00
2019-02-15 00:00:22 +01:00
const bfanalysis = this . blackframeTest ( ) ;
if ( bfanalysis . isBlack ) {
// we don't do any corrections on frames confirmed black
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] Black frame analysis suggests this frame is black or too dark. Doing nothing. ` , "color: #fa3" , bfanalysis ) ;
2019-02-15 00:00:22 +01:00
return ;
2018-05-09 00:58:50 +02:00
}
2018-02-28 23:54:32 +01:00
2019-05-03 00:49:33 +02:00
2019-02-15 00:00:22 +01:00
// if we are in fallback mode, then frame has already been drawn to the main canvas.
// if we are in normal mode though, the frame has yet to be drawn
if ( ! this . fallbackMode ) {
this . context . drawImage ( this . video , 0 , 0 , this . canvas . width , this . canvas . height ) ;
2018-05-09 00:58:50 +02:00
}
2019-02-15 00:00:22 +01:00
const imageData = this . context . getImageData ( 0 , 0 , this . canvas . width , this . canvas . height ) . data ;
2017-12-31 18:26:59 +01:00
2019-02-15 00:00:22 +01:00
if ( ! this . fastLetterboxPresenceTest ( imageData , sampleCols ) ) {
2018-05-09 00:58:50 +02:00
// Če ne zaznamo letterboxa, kličemo reset. Lahko, da je bilo razmerje stranic popravljeno na roke. Možno je tudi,
// da je letterbox izginil.
// If we don't detect letterbox, we reset aspect ratio to aspect ratio of the video file. The aspect ratio could
// have been corrected manually. It's also possible that letterbox (that was there before) disappeared.
2019-11-04 23:53:28 +01:00
this . conf . resizer . updateAr ( { type : AspectRatio . Automatic , ratio : this . getDefaultAr ( ) } ) ;
2019-02-15 00:00:22 +01:00
this . guardLine . reset ( ) ;
this . noLetterboxCanvasReset = true ;
2018-05-11 00:49:50 +02:00
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] Letterbox not detected in fast test. Letterbox is either gone or we manually corrected aspect ratio. Nothing will be done. ` , "color: #fa3" ) ;
2019-05-03 00:49:33 +02:00
2019-10-29 18:15:46 +01:00
this . clearImageData ( imageData ) ;
2017-12-31 18:26:59 +01:00
return ;
2018-05-09 00:58:50 +02:00
}
2018-07-15 15:18:40 +02:00
2018-05-09 00:58:50 +02:00
// Če preverjamo naprej, potem moramo postaviti to vrednost nazaj na 'false'. V nasprotnem primeru se bo
// css resetiral enkrat na video/pageload namesto vsakič, ko so za nekaj časa obrobe odstranejene
// if we look further we need to reset this value back to false. Otherwise we'll only get CSS reset once
// per video/pageload instead of every time letterbox goes away (this can happen more than once per vid)
2018-05-16 23:26:47 +02:00
this . noLetterboxCanvasReset = false ;
2018-02-12 23:28:31 +01:00
2018-05-09 00:58:50 +02:00
// poglejmo, če obrežemo preveč.
2019-02-15 00:00:22 +01:00
// let's check if we're cropping too much
const guardLineOut = this . guardLine . check ( imageData , this . fallbackMode ) ;
2018-05-11 00:49:50 +02:00
// če ni padla nobena izmed funkcij, potem se razmerje stranic ni spremenilo
// if both succeed, then aspect ratio hasn't changed.
if ( ! guardLineOut . imageFail && ! guardLineOut . blackbarFail ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] guardLine tests were successful. (no imagefail and no blackbarfail) \n ` , "color: #afa" , guardLineOut ) ;
2019-10-29 18:15:46 +01:00
this . clearImageData ( imageData ) ;
2018-05-11 00:49:50 +02:00
return ;
}
2019-02-15 00:00:22 +01:00
// drugače nadaljujemo, našemu vzorcu stolpcev pa dodamo tiste stolpce, ki so
// kršili blackbar (če obstajajo) ter jih razvrstimo
// otherwise we continue. We add blackbar violations to the list of the cols
// we'll sample and sort them
if ( guardLineOut . blackbarFail ) {
sampleCols . concat ( guardLineOut . offenders ) . sort ( ( a , b ) => a > b ) ;
}
// if we're in fallback mode and blackbar test failed, we restore CSS and quit
// (since the new letterbox edge isn't present in our sample due to technical
// limitations)
if ( this . fallbackMode && guardLineOut . blackbarFail ) {
2019-07-04 22:46:18 +02:00
this . conf . resizer . setAr ( { type : AspectRatio . Automatic , ratio : this . getDefaultAr ( ) } ) ;
2019-02-15 00:00:22 +01:00
this . guardLine . reset ( ) ;
this . noLetterboxCanvasReset = true ;
2019-10-29 18:15:46 +01:00
this . clearImageData ( imageData ) ;
2019-02-15 00:00:22 +01:00
return ;
}
2018-05-09 00:58:50 +02:00
// će se razmerje stranic spreminja iz ožjega na širšega, potem najprej poglejmo za prisotnostjo navpičnih črnih obrob.
// če so prisotne navpične obrobe tudi na levi in desni strani, potlej obstaja možnost, da gre za logo na črnem ozadju.
// v tem primeru obstaja nevarnost, da porežemo preveč. Ker obstaja dovolj velika možnost, da bi porezali preveč, rajši
// ne naredimo ničesar.
//
// če je pillarbox zaznan v primeru spremembe iz ožjega na širše razmerje stranice, razmerje povrnemo na privzeto vrednost.
//
// If aspect ratio changes from narrower to wider, we first check for presence of pillarbox. Presence of pillarbox indicates
// a chance of a logo on black background. We could cut easily cut too much. Because there's a somewhat significant chance
// that we will cut too much, we rather avoid doing anything at all. There's gonna be a next chance.
2018-05-16 23:26:47 +02:00
try {
2018-07-11 00:01:44 +02:00
if ( guardLineOut . blackbarFail || guardLineOut . imageFail ) {
2019-02-15 00:26:54 +01:00
if ( this . edgeDetector . findBars ( imageData , null , EdgeDetectPrimaryDirection . HORIZONTAL ) . status === 'ar_known' ) {
2017-09-24 01:54:46 +02:00
2018-05-09 00:58:50 +02:00
2018-07-11 00:01:44 +02:00
if ( guardLineOut . blackbarFail ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect' , ` [ArDetect::frameCheck] Detected blackbar violation and pillarbox. Resetting to default aspect ratio. ` ) ;
2019-07-04 22:46:18 +02:00
this . conf . resizer . setAr ( { type : AspectRatio . Automatic , ratio : this . getDefaultAr ( ) } ) ;
2018-07-11 00:01:44 +02:00
this . guardLine . reset ( ) ;
}
2018-03-04 23:07:11 +01:00
2018-07-11 00:01:44 +02:00
triggerTimeout = this . getTimeout ( baseTimeout , startTime ) ;
this . scheduleFrameCheck ( triggerTimeout ) ;
2019-10-29 18:15:46 +01:00
this . clearImageData ( imageData ) ;
2018-07-11 00:01:44 +02:00
return ;
}
2018-05-09 00:58:50 +02:00
}
2018-07-15 15:18:40 +02:00
} catch ( e ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect' , ` [ArDetect::frameCheck] something went wrong while checking for pillarbox. Error: \n ` , e ) ;
2018-07-15 15:18:40 +02:00
}
2018-03-04 23:07:11 +01:00
2018-05-09 00:58:50 +02:00
// pa poglejmo, kje se končajo črne letvice na vrhu in na dnu videa.
// let's see where black bars end.
2018-05-15 20:36:22 +02:00
this . sampleCols _current = sampleCols . length ;
2018-05-09 00:58:50 +02:00
// blackSamples -> {res_top, res_bottom}
2018-05-14 20:39:15 +02:00
2019-02-15 00:26:54 +01:00
var edgePost = this . edgeDetector . findBars ( imageData , sampleCols , EdgeDetectPrimaryDirection . VERTICAL , EdgeDetectQuality . IMPROVED , guardLineOut , bfanalysis ) ;
2018-05-09 00:58:50 +02:00
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] edgeDetector returned this \n ` , "color: #aaf" , edgePost ) ;
2018-05-09 00:58:50 +02:00
2019-02-15 00:00:22 +01:00
if ( edgePost . status !== EdgeStatus . AR _KNOWN ) {
// rob ni bil zaznan, zato ne naredimo ničesar.
// no edge was detected. Let's leave things as they were
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] Edge wasn't detected with findBars ` , "color: #fa3" , edgePost , "EdgeStatus.AR_KNOWN:" , EdgeStatus . AR _KNOWN ) ;
2019-10-29 18:15:46 +01:00
this . clearImageData ( imageData ) ;
2019-02-15 00:00:22 +01:00
return ;
}
2018-07-11 00:01:44 +02:00
2019-02-15 00:00:22 +01:00
var newAr = this . calculateArFromEdges ( edgePost ) ;
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] Triggering aspect ration change! new ar: ${ newAr } ` , "color: #aaf" ) ;
2019-02-15 00:00:22 +01:00
// we also know edges for guardline, so set them.
// we need to be mindful of fallbackMode though
2019-08-24 22:35:56 +02:00
// if edges are okay and not invalid, we also
// allow automatic aspect ratio correction. If edges
// are bogus, we remain aspect ratio unchanged.
try {
if ( ! this . fallbackMode ) {
// throws error if top/bottom are invalid
this . guardLine . setBlackbar ( { top : edgePost . guardLineTop , bottom : edgePost . guardLineBottom } ) ;
} else {
if ( this . conf . player . dimensions ) {
this . guardLine . setBlackbarManual ( {
top : this . settings . active . arDetect . fallbackMode . noTriggerZonePx ,
bottom : this . conf . player . dimensions . height - this . settings . active . arDetect . fallbackMode . noTriggerZonePx - 1
} , {
top : edgePost . guardLineTop + this . settings . active . arDetect . guardLine . edgeTolerancePx ,
bottom : edgePost . guardLineBottom - this . settings . active . arDetect . guardLine . edgeTolerancePx
} )
}
2019-02-15 00:00:22 +01:00
}
2019-08-24 22:35:56 +02:00
this . processAr ( newAr ) ;
} catch ( e ) {
// edges weren't gucci, so we'll just reset
// the aspect ratio to defaults
2019-10-29 18:15:10 +01:00
this . logger . log ( 'error' , 'arDetect' , ` %c[ArDetect::frameCheck] There was a problem setting blackbar. Doing nothing. Error: ` , e ) ;
2020-06-01 23:52:29 +02:00
try {
this . guardline . reset ( ) ;
} catch ( e ) {
// no guardline, no bigge
}
2019-10-29 18:15:10 +01:00
// WE DO NOT RESET ASPECT RATIO HERE IN CASE OF PROBLEMS, CAUSES UNWARRANTED RESETS:
// (eg. here: https://www.youtube.com/watch?v=nw5Z93Yt-UQ&t=410)
//
// this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
2019-02-15 00:00:22 +01:00
}
2019-10-29 18:15:10 +01:00
this . clearImageData ( imageData ) ;
2019-02-15 00:00:22 +01:00
}
resetBlackLevel ( ) {
this . blackLevel = this . settings . active . arDetect . blackbar . blackLevel ;
}
blackLevelTest _full ( ) {
}
blackframeTest ( ) {
2019-05-04 21:33:48 +02:00
if ( this . blackLevel === undefined ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , "[ArDetect::blackframeTest] black level undefined, resetting" ) ;
2019-02-15 00:00:22 +01:00
this . resetBlackLevel ( ) ;
}
2018-07-10 21:58:26 +02:00
2019-02-15 00:00:22 +01:00
const rows = this . blackframeCanvas . height ;
const cols = this . blackframeCanvas . width ;
2019-05-03 00:49:33 +02:00
const pixels = rows * cols ;
let cumulative _r = 0 , cumulative _g = 0 , cumulative _b = 0 ;
let max _r = 0 , max _g = 0 , max _b = 0 ;
let avg _r , avg _g , avg _b ;
let var _r = 0 , var _g = 0 , var _b = 0 ;
2019-02-15 00:00:22 +01:00
let pixelMax = 0 ;
let cumulativeValue = 0 ;
let blackPixelCount = 0 ;
const bfImageData = this . blackframeContext . getImageData ( 0 , 0 , cols , rows ) . data ;
2019-05-04 21:33:48 +02:00
const blackTreshold = this . blackLevel + this . settings . active . arDetect . blackbar . frameTreshold ;
2019-02-15 00:00:22 +01:00
2018-03-04 23:07:11 +01:00
2019-02-15 00:00:22 +01:00
// we do some recon for letterbox and pillarbox. While this can't determine whether letterbox/pillarbox exists
// with sufficient level of certainty due to small sample resolution, it can still give us some hints for later
let rowMax = new Array ( rows ) . fill ( 0 ) ;
let colMax = new Array ( cols ) . fill ( 0 ) ;
let r , c ;
2019-05-03 00:49:33 +02:00
2019-02-15 00:00:22 +01:00
for ( let i = 0 ; i < bfImageData . length ; i += 4 ) {
pixelMax = Math . max ( bfImageData [ i ] , bfImageData [ i + 1 ] , bfImageData [ i + 2 ] ) ;
2019-05-03 00:49:33 +02:00
bfImageData [ i + 3 ] = pixelMax ;
2019-02-15 00:00:22 +01:00
2019-05-04 21:33:48 +02:00
if ( pixelMax < blackTreshold ) {
if ( pixelMax < this . blackLevel ) {
this . blackLevel = pixelMax ;
}
2019-02-15 00:00:22 +01:00
blackPixelCount ++ ;
} else {
cumulativeValue += pixelMax ;
2019-05-03 00:49:33 +02:00
cumulative _r += bfImageData [ i ] ;
cumulative _g += bfImageData [ i + 1 ] ;
cumulative _b += bfImageData [ i + 2 ] ;
max _r = max _r > bfImageData [ i ] ? max _r : bfImageData [ i ] ;
max _g = max _g > bfImageData [ i + 1 ] ? max _g : bfImageData [ i + 1 ] ;
max _b = max _b > bfImageData [ i + 2 ] ? max _b : bfImageData [ i + 2 ] ;
2019-02-15 00:00:22 +01:00
}
2019-05-03 00:49:33 +02:00
r = ~ ~ ( i / rows ) ;
2019-02-15 00:00:22 +01:00
c = i % cols ;
if ( pixelMax > rowMax [ r ] ) {
rowMax [ r ] = pixelMax ;
}
if ( pixelMax > colMax [ c ] ) {
colMax [ c ] = colMax ;
}
2018-05-09 00:58:50 +02:00
}
2019-02-15 00:00:22 +01:00
2019-05-03 00:49:33 +02:00
max _r = 1 / ( max _r || 1 ) ;
max _g = 1 / ( max _g || 1 ) ;
max _b = 1 / ( max _b || 1 ) ;
const imagePixels = pixels - blackPixelCount ;
// calculate averages and normalize them
avg _r = ( cumulative _r / imagePixels ) * max _r ;
avg _g = ( cumulative _g / imagePixels ) * max _g ;
avg _b = ( cumulative _b / imagePixels ) * max _b ;
// second pass for color variance
for ( let i = 0 ; i < bfImageData . length ; i += 4 ) {
if ( bfImageData [ i + 3 ] >= this . blackLevel ) {
var _r += Math . abs ( avg _r - bfImageData [ i ] * max _r ) ;
var _g += Math . abs ( avg _g - bfImageData [ i + 1 ] * max _g ) ;
var _b += Math . abs ( avg _b - bfImageData [ i + 1 ] * max _b ) ;
}
}
const hasSufficientVariance = Math . abs ( var _r - var _g ) / Math . max ( var _r , var _g , 1 ) > this . settings . active . arDetect . blackframe . sufficientColorVariance
|| Math . abs ( var _r - var _b ) / Math . max ( var _r , var _b , 1 ) > this . settings . active . arDetect . blackframe . sufficientColorVariance
|| Math . abs ( var _b - var _g ) / Math . max ( var _b , var _g , 1 ) > this . settings . active . arDetect . blackframe . sufficientColorVariance
let isBlack = ( blackPixelCount / ( cols * rows ) > this . settings . active . arDetect . blackframe . blackPixelsCondition ) ;
if ( ! isBlack ) {
if ( hasSufficientVariance ) {
isBlack = cumulativeValue < this . settings . active . arDetect . blackframe . cumulativeThresholdLax ;
} else {
isBlack = cumulativeValue < this . settings . active . arDetect . blackframe . cumulativeThresholdStrict ;
}
}
if ( Debug . debug ) {
return {
isBlack : isBlack ,
blackPixelCount : blackPixelCount ,
blackPixelRatio : ( blackPixelCount / ( cols * rows ) ) ,
cumulativeValue : cumulativeValue ,
hasSufficientVariance : hasSufficientVariance ,
2019-05-04 21:33:48 +02:00
blackLevel : this . blackLevel ,
2019-05-03 00:49:33 +02:00
variances : {
raw : {
r : var _r , g : var _g , b : var _b
} ,
relative : {
rg : Math . abs ( var _r - var _g ) / Math . max ( var _r , var _g , 1 ) ,
rb : Math . abs ( var _r - var _b ) / Math . max ( var _r , var _b , 1 ) ,
gb : Math . abs ( var _b - var _g ) / Math . max ( var _b , var _g , 1 ) ,
} ,
relativePercent : {
rg : Math . abs ( var _r - var _g ) / Math . max ( var _r , var _g , 1 ) / this . settings . active . arDetect . blackframe . sufficientColorVariance ,
rb : Math . abs ( var _r - var _b ) / Math . max ( var _r , var _b , 1 ) / this . settings . active . arDetect . blackframe . sufficientColorVariance ,
gb : Math . abs ( var _b - var _g ) / Math . max ( var _b , var _g , 1 ) / this . settings . active . arDetect . blackframe . sufficientColorVariance ,
} ,
varianceLimit : this . settings . active . arDetect . blackframe . sufficientColorVariance ,
} ,
cumulativeValuePercent : cumulativeValue / ( hasSufficientVariance ? this . settings . active . arDetect . blackframe . cumulativeThresholdLax : this . settings . active . arDetect . blackframe . cumulativeThresholdStrict ) ,
rowMax : rowMax ,
colMax : colMax ,
} ;
}
2019-02-15 00:00:22 +01:00
return {
2019-05-03 00:49:33 +02:00
isBlack : isBlack ,
2019-02-15 00:00:22 +01:00
rowMax : rowMax ,
colMax : colMax ,
} ;
2018-05-09 00:58:50 +02:00
}
2018-03-04 23:07:11 +01:00
2019-02-15 00:00:22 +01:00
fastLetterboxPresenceTest ( imageData , sampleCols ) {
// fast test to see if aspect ratio is correct.
// returns 'true' if presence of letterbox is possible.
// returns 'false' if we found a non-black edge pixel.
// If we detect anything darker than blackLevel, we modify blackLevel to the new lowest value
const rowOffset = this . canvas . width * ( this . canvas . height - 1 ) ;
2019-02-22 23:02:48 +01:00
let currentMin = 255 , currentMax = 0 , colOffset _r , colOffset _g , colOffset _b , colOffset _rb , colOffset _gb , colOffset _bb , blthreshold = this . settings . active . arDetect . blackbar . threshold ;
2019-02-15 00:00:22 +01:00
2019-02-22 23:02:48 +01:00
// detect black level. if currentMax comes above blackbar + blackbar threshold, we know we aren't letterboxed
2019-02-15 00:00:22 +01:00
for ( var i = 0 ; i < sampleCols . length ; ++ i ) {
colOffset _r = sampleCols [ i ] << 2 ;
colOffset _g = colOffset _r + 1 ;
colOffset _b = colOffset _r + 2 ;
colOffset _rb = colOffset _r + rowOffset ;
colOffset _gb = colOffset _g + rowOffset ;
colOffset _bb = colOffset _b + rowOffset ;
currentMax = Math . max (
imageData [ colOffset _r ] , imageData [ colOffset _g ] , imageData [ colOffset _b ] ,
2019-02-15 00:26:54 +01:00
// imageData[colOffset_rb], imageData[colOffset_gb], imageData[colOffset_bb],
2019-02-15 00:00:22 +01:00
currentMax
) ;
2019-02-22 23:02:48 +01:00
if ( currentMax > this . blackLevel + blthreshold ) {
2019-02-15 00:00:22 +01:00
// we search no further
if ( currentMin < this . blackLevel ) {
this . blackLevel = currentMin ;
}
return false ;
}
currentMin = Math . min (
2019-02-15 00:26:54 +01:00
currentMax ,
2019-02-15 00:00:22 +01:00
currentMin
) ;
}
if ( currentMin < this . blackLevel )
this . blackLevel = currentMin
return true ;
2018-05-14 20:39:15 +02:00
}
2018-05-09 00:58:50 +02:00
}
2018-03-04 23:07:11 +01:00
2018-05-09 00:58:50 +02:00
var _ard _console _stop = "background: #000; color: #f41" ;
var _ard _console _start = "background: #000; color: #00c399" ;
2019-02-15 20:40:56 +01:00
var _ard _console _change = "background: #000; color: #ff8" ;
2018-03-04 23:07:11 +01:00
2018-12-31 01:03:07 +01:00
export default ArDetector ;