diff --git a/src/ext/lib/ar-detect/AardGl.js b/src/ext/lib/ar-detect/AardGl.js index 8deb0c6..f74645f 100644 --- a/src/ext/lib/ar-detect/AardGl.js +++ b/src/ext/lib/ar-detect/AardGl.js @@ -10,6 +10,7 @@ import VideoAlignment from '../../../common/enums/video-alignment.enum'; import AspectRatio from '../../../common/enums/aspect-ratio.enum'; import { generateHorizontalAdder } from './gllib/shader-generators/HorizontalAdderGenerator'; import { getVertexShader } from './gllib/shaders/vertex-shader'; +import { sleep } from '../Util'; /** * AardGl: Hardware accelerated aspect ratio detection script, based on WebGL @@ -45,6 +46,149 @@ class AardGl { this.logger.log('info', 'init', `[AardGl::ctor] creating new AardGl. arid: ${this.arid}`); } + /** + * + * HELPER FUNCTIONS + * + */ + //#region helpers + + 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; + } + + isRunning(){ + return ! (this._halted || this._paused || this._exited); + } + + 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{ + ths.main(); + } catch(e) { + this.logger('error', 'debug', `[AardGl::scheduleInitRestart] <@${this.arid}> Failed to start main(). Error:`,e); + } + ths = null; + }, + timeout + ); + } + + getTimeout(baseTimeout, startTime){ + var execTime = (performance.now() - startTime); + + return baseTimeout; + } + + async nextFrame() { + return new Promise(resolve => window.requestAnimationFrame(resolve)); + } + + getDefaultAr() { + return this.video.videoWidth / this.video.videoHeight; + } + + resetBlackLevel(){ + this.blackLevel = this.settings.active.arDetect.blackbar.blackLevel; + } + + clearImageData(id) { + if (ArrayBuffer.transfer) { + ArrayBuffer.transfer(id, 0); + } + id = undefined; + } + //#endregion + //#region canvas management + 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(){ + this.logger.log('info', 'debug', `%c[AardGl::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.`) + + return this.canvas.height == window.innerHeight + } + //#endregion + //#region aard control + + start() { + this.logger.log('info', 'debug', `"%c[AardGl::start] <@${this.arid}> Starting automatic aspect ratio detection`, _ard_console_start); + + if (this.conf.resizer.lastAr.type === AspectRatio.Automatic) { + // ensure first autodetection will run in any case + this.conf.resizer.setLastAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()}); + } + + + + // launch main() if it's currently not running: + this.main(); + // 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; + this._halted = false; + this._paused = false; + } + + stop(){ + this.logger.log('info', 'debug', `"%c[AardGl::stop] <@${this.arid}> Stopping automatic aspect ratio detection`, _ard_console_stop); + this._halted = true; + // this.conf.resizer.setArLastAr(); + } + + 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; + } + } + + unpause() { + // 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; + } + } + setManualTick(manualTick) { this._manualTicks = manualTick; } @@ -52,7 +196,70 @@ class AardGl { tick() { this._nextTick = true; } + //#endregion + //#region WebGL helpers + glSetRectangle(glContext, width, height) { + glContext.bufferData(glContext.ARRAY_BUFFER, new Float32Array([ + 0, 0, + width, 0, + 0, height, + 0, height, + width, 0, + width, height + ]), glContext.STATIC_DRAW); + } + /** + * Creates shader + * @param {*} glContext — gl context + * @param {*} shaderSource — shader code (as returned by a shader generator, for example) + * @param {*} shaderType — shader type (gl[context].FRAGMENT_SHADER or gl[context].VERTEX_SHADER) + */ + compileShader(glContext, shaderSource, shaderType) { + const shader = glContext.createShader(shaderType); + + // load source and compile shader + glContext.shaderSource(shader, shaderSource); + glContext.compileShader(shader); + + // check if shader was compiled successfully + if (! glContext.getShaderParameter(shader, gl.COMPILE_STATUS)) { + glContext.deleteShader(shader); + this.logger.log('error', ['init', 'debug', 'arDetect'], `%c[AardGl::setupShader] <@${this.arid}> Failed to setup shader.`, _ard_console_stop); + return null; + } + + return shader; + } + + /** + * Creates gl program + * @param {*} glContext — gl context + * @param {*} shaders — shaders (previously compiled with setupShader()) + */ + compileProgram(glContext, shaders) { + const program = glContext.createProgram(); + for (const shader of shadersr) { + glContext.attachShader(program, shader); + } + glContext.linkProgram(program); + if (! glContext.getProgramParameter(program, glContext.LINK_STATUS)) { + glContext.deleteShader(shader); + this.logger.log('error', ['init', 'debug', 'arDetect'], `%c[AardGl::setupProgram] <@${this.arid}> Failed to setup program.`, _ard_console_stop); + return null; + } + + return program; + } + //#endregion + + /* + * -------------------- + * SETUP AND CLEANUP + * -------------------- + */ + + //#region init and destroy init(){ this.logger.log('info', 'init', `[AardGl::init] <@${this.arid}> Initializing autodetection.`); @@ -72,6 +279,7 @@ class AardGl { // this.debugCanvas.destroy(); this.stop(); } + //#endregion setup(cwidth, cheight){ this.logger.log('info', 'init', `[AardGl::setup] <@${this.arid}> Starting autodetection setup.`); @@ -212,106 +420,6 @@ class AardGl { this.conf.arSetupComplete = true; } - glSetRectangle(glContext, width, height) { - glContext.bufferData(glContext.ARRAY_BUFFER, new Float32Array([ - 0, 0, - width, 0, - 0, height, - 0, height, - width, 0, - width, height - ]), glContext.STATIC_DRAW); - } - - /** - * Creates shader - * @param {*} glContext — gl context - * @param {*} shaderSource — shader code (as returned by a shader generator, for example) - * @param {*} shaderType — shader type (gl[context].FRAGMENT_SHADER or gl[context].VERTEX_SHADER) - */ - compileShader(glContext, shaderSource, shaderType) { - const shader = glContext.createShader(shaderType); - - // load source and compile shader - glContext.shaderSource(shader, shaderSource); - glContext.compileShader(shader); - - // check if shader was compiled successfully - if (! glContext.getShaderParameter(shader, gl.COMPILE_STATUS)) { - glContext.deleteShader(shader); - this.logger.log('error', ['init', 'debug', 'arDetect'], `%c[AardGl::setupShader] <@${this.arid}> Failed to setup shader.`, _ard_console_stop); - return null; - } - - return shader; - } - - /** - * Creates gl program - * @param {*} glContext — gl context - * @param {*} shaders — shaders (previously compiled with setupShader()) - */ - compileProgram(glContext, shaders) { - const program = glContext.createProgram(); - for (const shader of shadersr) { - glContext.attachShader(program, shader); - } - glContext.linkProgram(program); - if (! glContext.getProgramParameter(program, glContext.LINK_STATUS)) { - glContext.deleteShader(shader); - this.logger.log('error', ['init', 'debug', 'arDetect'], `%c[AardGl::setupProgram] <@${this.arid}> Failed to setup program.`, _ard_console_stop); - return null; - } - - return program; - } - - start() { - this.logger.log('info', 'debug', `"%c[AardGl::start] <@${this.arid}> Starting automatic aspect ratio detection`, _ard_console_start); - - if (this.conf.resizer.lastAr.type === AspectRatio.Automatic) { - // ensure first autodetection will run in any case - this.conf.resizer.setLastAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()}); - } - - - - // launch main() if it's currently not running: - this.main(); - // 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; - this._halted = false; - this._paused = false; - } - - unpause() { - // 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; - } - } - - 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; - } - } - - stop(){ - this.logger.log('info', 'debug', `"%c[AardGl::stop] <@${this.arid}> Stopping automatic aspect ratio detection`, _ard_console_stop); - this._halted = true; - // this.conf.resizer.setArLastAr(); - } - - async nextFrame() { - return new Promise(resolve => window.requestAnimationFrame(resolve)); - } - async main() { if (this._paused) { // unpause if paused @@ -328,7 +436,7 @@ class AardGl { while (!this._exited && exitedRetries --> 0) { this.logger.log('warn', 'debug', `[AardGl::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 ...`); - await this.sleep(this.settings.active.arDetect.timers.tickrate); + await sleep(this.settings.active.arDetect.timers.tickrate); } if (!this._exited) { this.logger.log('error', 'debug', `[AardGl::main] <@${this.arid}> Previous instance didn't exit in time. Not starting a new one.`); @@ -380,91 +488,78 @@ class AardGl { this._exited = true; } - async sleep(timeout) { - return new Promise( (resolve, reject) => setTimeout(() => resolve(), timeout)); - } + frameCheck(){ + if(! this.video){ + this.logger.log('error', 'debug', `%c[AardGl::frameCheck] <@${this.arid}> Video went missing. Destroying current instance of videoData.`); + this.conf.destroy(); + return; + } - 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; + if (!this.blackframeContext) { + this.init(); } - return Date.now() - lastFrameCheckStartTime > this.settings.active.arDetect.timers.playing; - } + var startTime = performance.now(); + let sampleCols = this.sampleCols.slice(0); - isRunning(){ - return ! (this._halted || this._paused || this._exited); - } + // + // [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); + this.fallbackMode = false; + } catch (e) { + this.logger.log('error', 'arDetect', `%c[AardGl::frameCheck] <@${this.arid}> %c[AardGl::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); - - 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{ - ths.main(); - } catch(e) { - this.logger('error', 'debug', `[AardGl::scheduleInitRestart] <@${this.arid}> Failed to start main(). Error:`,e); + // nothing to see here, really, if fallback mode isn't supported by browser + if (! this.canDoFallbackMode) { + return; + } + if (! this.canvasReadyForDrawWindow()) { + // this means canvas needs to be resized, so we'll just re-run setup with all those new parameters + this.stop(); + + let newCanvasWidth = window.innerHeight * (this.video.videoWidth / this.video.videoHeight); + let newCanvasHeight = window.innerHeight; + + 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; } - ths = null; - }, - timeout - ); - } - - - - //#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(){ - this.logger.log('info', 'debug', `%c[AardGl::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.`) - - return this.canvas.height == window.innerHeight - } - - getTimeout(baseTimeout, startTime){ - var execTime = (performance.now() - startTime); - - return baseTimeout; - } - //#endregion - - getDefaultAr() { - return this.video.videoWidth / this.video.videoHeight; + + this.setup(newCanvasWidth, newCanvasHeight); + + 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) { + this.logger.log('error', 'arDetect', `%c[AardGl::frameCheck] can't draw image on canvas with fallback mode either. This error is prolly only temporary.`, "color:#000; backgroud:#f51;", e); + return; // it's prolly just a fluke, so we do nothing special here + } + // draw blackframe sample from our main sample: + this.blackframeContext.drawImage(this.canvas, this.blackframeCanvas.width, this.blackframeCanvas.height) + + this.logger.log('info', 'arDetect_verbose', `%c[AardGl::frameCheck] canvas.drawImage seems to have worked`, "color:#000; backgroud:#2f5;"); + } + + + this.clearImageData(imageData); } + /** + * ------------------------- + * DATA PROCESSING HELPERS + * ------------------------- + */ + //#region result processing calculateArFromEdges(edges) { // if we don't specify these things, they'll have some default values. if(edges.top === undefined){ @@ -562,418 +657,10 @@ class AardGl { this.conf.resizer.updateAr({type: AspectRatio.Automatic, ratio: trueAr}, {type: AspectRatio.Automatic, ratio: trueAr}); } + //#endregion + //#region data processing / frameCheck helpers - clearImageData(id) { - if (ArrayBuffer.transfer) { - ArrayBuffer.transfer(id, 0); - } - id = undefined; - } - - frameCheck(){ - if(! this.video){ - this.logger.log('error', 'debug', `%c[AardGl::frameCheck] <@${this.arid}> Video went missing. Destroying current instance of videoData.`); - this.conf.destroy(); - return; - } - - if (!this.blackframeContext) { - this.init(); - } - - var startTime = performance.now(); - let sampleCols = this.sampleCols.slice(0); - - // - // [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); - this.fallbackMode = false; - } catch (e) { - this.logger.log('error', 'arDetect', `%c[AardGl::frameCheck] <@${this.arid}> %c[AardGl::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); - - // nothing to see here, really, if fallback mode isn't supported by browser - if (! this.canDoFallbackMode) { - return; - } - if (! this.canvasReadyForDrawWindow()) { - // this means canvas needs to be resized, so we'll just re-run setup with all those new parameters - this.stop(); - - let newCanvasWidth = window.innerHeight * (this.video.videoWidth / this.video.videoHeight); - let newCanvasHeight = window.innerHeight; - - 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; - } - - this.setup(newCanvasWidth, newCanvasHeight); - - 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) { - this.logger.log('error', 'arDetect', `%c[AardGl::frameCheck] can't draw image on canvas with fallback mode either. This error is prolly only temporary.`, "color:#000; backgroud:#f51;", e); - return; // it's prolly just a fluke, so we do nothing special here - } - // draw blackframe sample from our main sample: - this.blackframeContext.drawImage(this.canvas, this.blackframeCanvas.width, this.blackframeCanvas.height) - - this.logger.log('info', 'arDetect_verbose', `%c[AardGl::frameCheck] canvas.drawImage seems to have worked`, "color:#000; backgroud:#2f5;"); - } - - const bfanalysis = this.blackframeTest(); - if (bfanalysis.isBlack) { - // we don't do any corrections on frames confirmed black - this.logger.log('info', 'arDetect_verbose', `%c[AardGl::frameCheck] Black frame analysis suggests this frame is black or too dark. Doing nothing.`, "color: #fa3", bfanalysis); - return; - } - - - - // 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); - } - const imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height).data; - - if (! this.fastLetterboxPresenceTest(imageData, sampleCols) ) { - // Č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. - this.conf.resizer.updateAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()}); - this.guardLine.reset(); - this.noLetterboxCanvasReset = true; - - this.logger.log('info', 'arDetect_verbose', `%c[AardGl::frameCheck] Letterbox not detected in fast test. Letterbox is either gone or we manually corrected aspect ratio. Nothing will be done.`, "color: #fa3"); - - this.clearImageData(imageData); - return; - } - - // Č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) - this.noLetterboxCanvasReset = false; - - // poglejmo, če obrežemo preveč. - // let's check if we're cropping too much - const guardLineOut = this.guardLine.check(imageData, this.fallbackMode); - - // č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) { - this.logger.log('info', 'arDetect_verbose', `%c[AardGl::frameCheck] guardLine tests were successful. (no imagefail and no blackbarfail)\n`, "color: #afa", guardLineOut); - this.clearImageData(imageData); - return; - } - - // 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) { - this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()}); - this.guardLine.reset(); - this.noLetterboxCanvasReset = true; - - this.clearImageData(imageData); - return; - } - - // ć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. - try{ - if(guardLineOut.blackbarFail || guardLineOut.imageFail){ - if(this.edgeDetector.findBars(imageData, null, EdgeDetectPrimaryDirection.HORIZONTAL).status === 'ar_known'){ - - - if(guardLineOut.blackbarFail){ - this.logger.log('info', 'arDetect', `[AardGl::frameCheck] Detected blackbar violation and pillarbox. Resetting to default aspect ratio.`); - this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()}); - this.guardLine.reset(); - } - - triggerTimeout = this.getTimeout(baseTimeout, startTime); - this.scheduleFrameCheck(triggerTimeout); - this.clearImageData(imageData); - return; - } - } - } catch(e) { - this.logger.log('info', 'arDetect', `[AardGl::frameCheck] something went wrong while checking for pillarbox. Error:\n`, e); - } - - // pa poglejmo, kje se končajo črne letvice na vrhu in na dnu videa. - // let's see where black bars end. - this.sampleCols_current = sampleCols.length; - - // blackSamples -> {res_top, res_bottom} - - var edgePost = this.edgeDetector.findBars(imageData, sampleCols, EdgeDetectPrimaryDirection.VERTICAL, EdgeDetectQuality.IMPROVED, guardLineOut, bfanalysis); - - this.logger.log('info', 'arDetect_verbose', `%c[AardGl::frameCheck] edgeDetector returned this\n`, "color: #aaf", edgePost); - - 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 - this.logger.log('info', 'arDetect_verbose', `%c[AardGl::frameCheck] Edge wasn't detected with findBars`, "color: #fa3", edgePost, "EdgeStatus.AR_KNOWN:", EdgeStatus.AR_KNOWN); - - this.clearImageData(imageData); - return; - } - - var newAr = this.calculateArFromEdges(edgePost); - - this.logger.log('info', 'arDetect_verbose', `%c[AardGl::frameCheck] Triggering aspect ration change! new ar: ${newAr}`, "color: #aaf"); - - // we also know edges for guardline, so set them. - // we need to be mindful of fallbackMode though - // 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 - }) - } - } - - this.processAr(newAr); - } catch (e) { - // edges weren't gucci, so we'll just reset - // the aspect ratio to defaults - this.logger.log('error', 'arDetect', `%c[AardGl::frameCheck] There was a problem setting blackbar. Doing nothing. Error:`, e); - - this.guardline.reset(); - // 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()}); - } - - this.clearImageData(imageData); - } - - resetBlackLevel(){ - this.blackLevel = this.settings.active.arDetect.blackbar.blackLevel; - } - - blackLevelTest_full() { - - } - - blackframeTest() { - if (this.blackLevel === undefined) { - this.logger.log('info', 'arDetect_verbose', "[AardGl::blackframeTest] black level undefined, resetting"); - this.resetBlackLevel(); - } - - const rows = this.blackframeCanvas.height; - const cols = this.blackframeCanvas.width; - 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; - - let pixelMax = 0; - let cumulativeValue = 0; - let blackPixelCount = 0; - const bfImageData = this.blackframeContext.getImageData(0, 0, cols, rows).data; - const blackTreshold = this.blackLevel + this.settings.active.arDetect.blackbar.frameTreshold; - - - // 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; - - - for (let i = 0; i < bfImageData.length; i+= 4) { - pixelMax = Math.max(bfImageData[i], bfImageData[i+1], bfImageData[i+2]); - bfImageData[i+3] = pixelMax; - - if (pixelMax < blackTreshold) { - if (pixelMax < this.blackLevel) { - this.blackLevel = pixelMax; - } - blackPixelCount++; - } else { - cumulativeValue += pixelMax; - 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]; - } - - r = ~~(i/rows); - c = i % cols; - - if (pixelMax > rowMax[r]) { - rowMax[r] = pixelMax; - } - if (pixelMax > colMax[c]) { - colMax[c] = colMax; - } - } - - 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, - blackLevel: this.blackLevel, - 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, - }; - } - return { - isBlack: isBlack, - rowMax: rowMax, - colMax: colMax, - }; - } - - 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); - let currentMin = 255, currentMax = 0, colOffset_r, colOffset_g, colOffset_b, colOffset_rb, colOffset_gb, colOffset_bb, blthreshold = this.settings.active.arDetect.blackbar.threshold; - - // detect black level. if currentMax comes above blackbar + blackbar threshold, we know we aren't letterboxed - - 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], - // imageData[colOffset_rb], imageData[colOffset_gb], imageData[colOffset_bb], - currentMax - ); - - if (currentMax > this.blackLevel + blthreshold) { - // we search no further - if (currentMin < this.blackLevel) { - this.blackLevel = currentMin; - } - return false; - } - - currentMin = Math.min( - currentMax, - currentMin - ); - } - - if (currentMin < this.blackLevel) - this.blackLevel = currentMin - - return true; - } - + //#endregion } var _ard_console_stop = "background: #000; color: #f41";