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' ;
import DebugCanvas from './DebugCanvas' ;
2019-02-15 00:00:22 +01:00
import VideoAlignment from '../../../common/enums/video-alignment.enum' ;
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
2018-05-08 23:35:16 +02:00
constructor ( videoData ) {
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
// todo: dynamically detect the following two
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 ;
this . canDoFallbackMode = false ;
2018-09-23 19:46:40 +02:00
if ( Debug . init ) {
console . log ( "[ArDetector::ctor] creating new ArDetector. arid:" , this . arid ) ;
}
2018-05-09 00:34:22 +02:00
}
2018-05-08 23:35:16 +02:00
init ( ) {
2018-09-23 19:46:40 +02:00
if ( Debug . debug || Debug . init ) {
console . log ( "[ArDetect::init] Initializing autodetection. arid:" , this . arid ) ;
2018-05-14 20:39:15 +02:00
}
2019-02-15 00:00:22 +01:00
try {
this . setup ( ) ;
} catch ( e ) {
console . log ( "[ArDetect::init] INITIALIZATION FAILED!\n" , e ) ;
}
2018-05-08 23:35:16 +02:00
}
2018-05-16 23:26:47 +02:00
destroy ( ) {
2018-09-23 19:46:40 +02:00
if ( Debug . debug || Debug . init ) {
console . log ( ` [ArDetect::destroy] <arid: ${ this . arid } > ` )
}
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
}
2018-06-27 23:55:37 +02:00
setup ( cwidth , cheight , forceStart ) {
2018-09-23 19:46:40 +02:00
if ( Debug . debug || Debug . init ) {
console . log ( "[ArDetect::setup] Starting autodetection setup. arid:" , this . arid ) ;
2018-05-14 20:39:15 +02:00
}
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 ) {
if ( Debug . debug ) {
console . log ( "[ArDetector::setup] video has no width or height!" , this . video . videoWidth , "× " , this . video . videoHeight )
2018-07-11 00:01:44 +02:00
}
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 ) {
if ( Debug . debug ) {
console . log ( "%c[ArDetect::setup] WARNING: CANVAS RESET DETECTED - recalculating guardLine" , "background: #000; color: #ff2" )
2018-06-27 23:55:37 +02:00
}
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
this . conf . resizer . setLastAr ( { type : "auto" , ar : null } ) ;
this . canvasImageDataRowLength = cwidth << 2 ;
this . noLetterboxCanvasReset = false ;
if ( forceStart || this . settings . canStartAutoAr ( ) ) {
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
}
start ( ) {
2018-07-11 23:13:40 +02:00
if ( Debug . debug ) {
console . log ( "%c[ArDetect::setup] Starting automatic aspect ratio detection." , _ard _console _start ) ;
}
2018-07-10 20:36:12 +02:00
this . conf . resizer . resetLastAr ( ) ;
2019-02-15 00:00:22 +01:00
// launch main() if it's currently not running:
this . main ( ) ;
this . _halted = false ;
this . _paused = false ;
2018-05-08 23:35:16 +02:00
}
2018-07-11 23:13:40 +02:00
unpause ( ) {
if ( this . _paused ) { // resume only if we explicitly paused
this . start ( ) ;
}
}
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 ;
2019-02-15 00:00:22 +01:00
this . conf . resizer . resetLastAr ( ) ;
2018-07-11 23:13:40 +02:00
}
}
2018-05-08 23:35:16 +02:00
stop ( ) {
if ( Debug . debug ) {
console . log ( "%c[ArDetect::_ard_stop] Stopping automatic aspect ratio detection" , _ard _console _stop ) ;
}
this . _halted = true ;
2018-07-10 20:36:12 +02:00
this . conf . resizer . resetLastAr ( ) ;
2018-05-08 23:35:16 +02:00
}
2019-02-15 00:00:22 +01:00
async main ( ) {
if ( this . paused ) {
// unpause if paused
this . _paused = false ;
}
if ( ! this . _halted ) {
// we are already running, don't run twice
return ;
}
const exitedRetries = 10 ;
while ( ! this . _exited && exitedRetries -- > 0 ) {
if ( Debug . debug ) {
console . log ( "[ArDetector::main] 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 ) ;
}
if ( ! this . _exited ) {
if ( Debug . debug ) {
console . log ( "[ArDetector::main] Previous instance didn't exit in time. Not starting a new one." )
}
return ;
}
if ( Debug . debug ) {
console . log ( "%c[ArDetect::main] Main autodetection loop started." , _ard _console _start ) ;
}
// 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 ) ;
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.
if ( this . canTriggerFrameCheck ( lastFrameCheckStartTime ) ) {
lastFrameCheckStartTime = Date . now ( ) ;
this . frameCheck ( ) ;
}
await this . sleep ( this . settings . active . arDetect . timers . tickrate ) ;
}
if ( Debug . debug ) {
console . log ( ` %c[ArDetect::main] Main autodetection loop exited. Halted? ${ this . _halted } ` , _ard _console _stop ) ;
}
this . _exited = true ;
}
async sleep ( timeout ) {
return new Promise ( ( resolve , reject ) => setTimeout ( ( ) => resolve ( ) , timeout ) ) ;
}
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-02-15 00:00:22 +01:00
ths . main ( ) ;
2018-05-20 23:17:09 +02:00
} catch ( e ) { console . log ( "[ArDetector::scheduleInitRestart] Failed to start init(). Error:" , e ) }
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 ( ) {
if ( Debug . debug )
console . log ( "%c[ArDetect::_ard_canvasReadyForDrawWindow] (?)" , "color: #44f" , this . canvas . height == window . innerHeight , "(ard_height:" , this . canvas . height , "| window height:" , window . innerHeight , ")" ) ;
return this . canvas . height == window . innerHeight
}
2018-05-09 00:58:50 +02:00
getTimeout ( baseTimeout , startTime ) {
var execTime = ( performance . now ( ) - startTime ) ;
2018-08-05 23:48:56 +02:00
if ( execTime > this . settings . active . arDetect . autoDisable . maxExecutionTime ) {
2018-07-15 15:18:40 +02:00
// this.detectionTimeoutEventCount++;
2018-05-09 00:58:50 +02:00
if ( Debug . debug ) {
2018-05-17 23:09:04 +02:00
console . log ( "[ArDetect::getTimeout] Exec time exceeded maximum allowed execution time. This has now happened " + this . detectionTimeoutEventCount + " times in a row." ) ;
2018-05-09 00:58:50 +02:00
}
2018-08-05 23:48:56 +02:00
// if( this.detectionTimeoutEventCount >= this.settings.active.arDetect.autoDisable.consecutiveTimeoutCount ){
2018-05-15 20:36:22 +02:00
// if (Debug.debug){
// console.log("[ArDetect::getTimeout] Maximum execution time was exceeded too many times. Automatic aspect ratio detection has been disabled.");
// }
2018-05-09 00:58:50 +02:00
2018-05-15 20:36:22 +02:00
// Comms.sendToBackgroundScript({cmd: 'disable-autoar', reason: 'Automatic aspect ratio detection was taking too much time and has been automatically disabled in order to avoid lag.'});
// _ard_stop();
// return 999999;
// }
2018-05-09 00:58:50 +02:00
} else {
this . detectionTimeoutEventCount = 0 ;
}
2018-08-05 23:48:56 +02:00
// return baseTimeout > this.settings.active.arDetect.minimumTimeout ? baseTimeout : this.settings.active.arDetect.minimumTimeout;
2018-05-09 00:58:50 +02:00
return baseTimeout ;
}
2018-05-09 00:34:22 +02:00
//#endregion
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 ;
edge . left = 0 ;
edges . right = 0 ;
}
2018-07-11 00:01:44 +02:00
2018-07-11 23:13:40 +02:00
let zoomFactor = 1 ;
2018-05-09 00:34:22 +02:00
var letterbox = edges . top + edges . bottom ;
2018-07-11 23:13:40 +02:00
if ( this . fallbackMode ) {
// 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 ;
}
var trueHeight = this . canvas . height * zoomFactor - letterbox ;
2018-07-11 00:01:44 +02:00
2018-05-09 00:34:22 +02:00
if ( this . fallbackMode ) {
2018-08-05 23:48:56 +02:00
if ( edges . top > 1 && edges . top <= this . settings . active . arDetect . fallbackMode . noTriggerZonePx ) {
2018-07-15 16:22:32 +02:00
if ( Debug . debug && Debug . debugArDetect ) {
console . log ( "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-05-09 00:34:22 +02:00
}
2018-07-11 00:01:44 +02:00
2018-07-11 23:13:40 +02:00
return this . canvas . width * zoomFactor / trueHeight ;
2018-07-11 00:01:44 +02:00
}
processAr ( trueAr ) {
2018-07-11 23:13:40 +02:00
let actualHeight = 0 ; // purely for fallback mode
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 ( ) ;
2018-05-09 00:34:22 +02:00
if ( lastAr . type == "auto" && lastAr . ar != null ) {
// 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
if ( Debug . debug && Debug . debugArDetect )
console . log ( "%c[ArDetect::_ard_processAr] 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 ) {
2018-05-09 00:34:22 +02:00
if ( Debug . debug && Debug . debugArDetect )
console . log ( "%c[ArDetect::_ard_processAr] aspect ratio change denied — diff %:" , "background: #740; color: #fa2" , arDiff _percent )
return ;
}
else if ( Debug . debug && Debug . debugArDetect ) {
console . log ( "%c[ArDetect::_ard_processAr] aspect ratio change accepted — diff %:" , "background: #153; color: #4f9" , arDiff _percent )
}
}
if ( Debug . debug )
console . log ( "[ArDetect::_ard_processAr] attempting to fix aspect ratio. New aspect ratio: " , trueAr ) ;
2018-05-14 20:39:15 +02:00
this . conf . resizer . setAr ( trueAr , { type : "auto" , ar : trueAr } ) ;
2018-05-09 00:34:22 +02:00
}
frameCheck ( ) {
2018-05-09 00:58:50 +02:00
if ( ! this . video ) {
if ( Debug . debug || Debug . warnings _critical )
2018-05-16 23:26:47 +02:00
console . log ( "[ArDetect::_ard_vdraw] Video went missing. Destroying current instance of videoData." )
this . conf . destroy ( ) ;
2018-05-09 00:58:50 +02:00
return ;
}
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 ) ;
this . fallbackMode = false ;
} catch ( e ) {
if ( Debug . debug && Debug . debugArDetect ) {
console . log ( ` %c[ArDetect::_ard_vdraw] 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 ) ;
2018-07-15 16:22:32 +02:00
}
2019-02-15 00:00:22 +01:00
// 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 ( ) ;
2018-05-09 00:58:50 +02:00
2019-02-15 00:00:22 +01:00
var newCanvasWidth = window . innerHeight * ( this . video . videoWidth / this . video . videoHeight ) ;
var 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 ;
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 ) {
console . log ( ` %c[ArDetect::_ard_vdraw] 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
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-02-15 00:00:22 +01:00
if ( Debug . debug ) {
console . log ( "%c[ArDetect::_ard_vdraw] 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 ( ) ;
2018-02-26 22:38:17 +01:00
2019-02-15 00:00:22 +01:00
if ( bfanalysis . isBlack ) {
// we don't do any corrections on frames confirmed black
return ;
2018-05-09 00:58:50 +02:00
}
2018-02-28 23:54:32 +01: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-02-15 00:26:54 +01:00
console . log ( "FAST LETTERBOX PRESENCE TEST FAILED, CALLING RESET" )
this . conf . resizer . reset ( { type : "auto" , ar : null } ) ;
2019-02-15 00:00:22 +01:00
this . guardLine . reset ( ) ;
this . noLetterboxCanvasReset = true ;
2018-05-11 00:49:50 +02:00
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 ) {
2018-07-15 15:18:40 +02:00
if ( Debug . debug && Debug . debugArDetect ) {
console . log ( ` %c[ArDetect::_ard_vdraw] guardLine tests were successful. (no imagefail and no blackbarfail) \n ` , "color: #afa" , guardLineOut ) ;
}
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-02-15 00:26:54 +01:00
this . conf . resizer . reset ( { type : "auto" , ar : null } ) ;
2019-02-15 00:00:22 +01:00
this . guardLine . reset ( ) ;
this . noLetterboxCanvasReset = true ;
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-07-11 00:01:44 +02:00
if ( Debug . debug && guardLineOut . blackbarFail ) {
console . log ( "[ArDetect::_ard_vdraw] Detected blackbar violation and pillarbox. Resetting to default aspect ratio." ) ;
}
2018-05-09 00:58:50 +02:00
2018-07-11 00:01:44 +02:00
if ( guardLineOut . blackbarFail ) {
2019-02-15 00:26:54 +01:00
this . conf . resizer . reset ( { type : "auto" , ar : null } ) ;
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 ) ;
return ;
}
2018-05-09 00:58:50 +02:00
}
2018-07-15 15:18:40 +02:00
} catch ( e ) {
if ( Debug . debug ) {
console . log ( "[ArDetect.js::frameCheck] something went wrong when checking for pillarbox. Error:\n" , e )
}
}
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
2018-07-15 15:18:40 +02:00
if ( Debug . debug && Debug . debugArDetect ) {
console . log ( ` %c[ArDetect::_ard_vdraw] edgeDetector returned this \n ` , "color: #aaf" , edgePost ) ;
}
2019-02-15 00:26:54 +01:00
// console.log("SAMPLES:", blackbarSamples, "candidates:", edgeCandidates, "post:", edgePost,"\n\nblack level:", this.blackLevel, "tresh:", this.blackLevel + this.settings.active.arDetect.blackbar.treshold);
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
return ;
}
2018-07-11 00:01:44 +02:00
2019-02-15 00:00:22 +01:00
var newAr = this . calculateArFromEdges ( edgePost ) ;
2018-03-04 23:07:11 +01:00
2019-02-15 00:00:22 +01:00
// if (this.fallbackMode
// && (!guardLineOut.blackbarFail && guardLineOut.imageFail)
// && newAr < this.conf.resizer.getLastAr().ar
// ) {
// // V primeru nesmiselnih rezultatov tudi ne naredimo ničesar.
// // v fallback mode se lahko naredi, da je novo razmerje stranice ožje kot staro, kljub temu da je šel
// // blackbar test skozi. Spremembe v tem primeru ne dovolimo.
// //
// // (Pravilen fix? Popraviti je treba računanje robov. V fallback mode je treba upoštevati, da obrobe,
// // ki smo jih obrezali, izginejo is canvasa)
// //
// // NOTE: pravilen fix je bil implementiran
// return;
// }
if ( Debug . debug && Debug . debugArDetect ) {
console . log ( ` %c[ArDetect::_ard_vdraw] Triggering aspect ration change! new ar: ${ newAr } ` , "color: #aaf" ) ;
}
this . processAr ( newAr ) ;
// we also know edges for guardline, so set them.
// we need to be mindful of fallbackMode though
if ( ! this . fallbackMode ) {
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
} )
}
}
2018-03-04 23:07:11 +01:00
2019-02-15 00:00:22 +01:00
// }
// else{
// console.log("detected text on edges, dooing nothing")
// }
}
resetBlackLevel ( ) {
this . blackLevel = this . settings . active . arDetect . blackbar . blackLevel ;
}
blackLevelTest _full ( ) {
}
blackframeTest ( ) {
if ( ! this . blackLevel ) {
if ( Debug . debug && Debug . debugArDetect ) {
console . log ( "[ArDetect::_ard_vdraw] black level undefined, resetting" ) ;
2018-07-11 00:01:44 +02:00
}
2018-05-09 00:58:50 +02:00
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 ;
let pixelMax = 0 ;
let cumulativeValue = 0 ;
let blackPixelCount = 0 ;
const bfImageData = this . blackframeContext . getImageData ( 0 , 0 , cols , rows ) . data ;
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 ;
for ( let i = 0 ; i < bfImageData . length ; i += 4 ) {
pixelMax = Math . max ( bfImageData [ i ] , bfImageData [ i + 1 ] , bfImageData [ i + 2 ] ) ;
if ( pixelMax < this . blackLevel ) {
this . blackLevel = pixelMax ;
blackPixelCount ++ ;
} else {
cumulativeValue += pixelMax ;
}
r = Math . floor ( i / rows ) ;
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
return {
isBlack : ( blackPixelCount / ( cols * rows ) > this . settings . active . arDetect . blackframe . blackPixelsCondition ) ? true : cumulativeValue < this . settings . active . arDetect . blackframe . cumulativeTreshold ,
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 ) ;
let currentMin = 255 , currentMax = 0 , colOffset _r , colOffset _g , colOffset _b , colOffset _rb , colOffset _gb , colOffset _bb , bltreshold = this . settings . active . arDetect . blackbar . treshold ;
// detect black level. if currentMax comes above blackbar + blackbar treshold, 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 ] ,
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
) ;
if ( currentMax > this . blackLevel + bltreshold ) {
2019-02-15 00:26:54 +01:00
console . log ( "CURRENT MAX:" , currentMax , "BLACK LEVEL, treshold, bl+t" , this . blackLevel , bltreshold , this . blackLevel + bltreshold )
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" ;
2018-03-04 23:07:11 +01:00
2018-12-31 01:03:07 +01:00
export default ArDetector ;