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';
2021-02-08 23:04:54 +01:00
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum' ;
import AspectRatioType from '../../../common/enums/AspectRatioType.enum' ;
2021-02-18 22:29:23 +01:00
import { sleep } from '../Util' ;
2020-12-22 23:23:03 +01:00
import BrowserDetect from '../../conf/BrowserDetect' ;
2021-02-18 22:29:23 +01:00
import Logger from '../Logger' ;
import VideoData from '../video-data/VideoData' ;
import Settings from '../Settings' ;
2018-12-31 01:03:07 +01:00
2021-08-25 22:30:06 +02:00
enum VideoPlaybackState {
Playing ,
Paused ,
Ended ,
Error
}
2018-05-08 23:35:16 +02:00
class ArDetector {
2021-02-18 22:29:23 +01:00
logger : Logger ;
conf : VideoData ;
video : HTMLVideoElement ;
settings : Settings ;
guardLine : GuardLine ;
edgeDetector : EdgeDetect ;
setupTimer : any ;
sampleCols : any [ ] ;
sampleLines
canFallback : boolean = true ;
fallbackMode : boolean = false ;
blackLevel : number ;
arid : string ;
// ar detector starts in this state. running main() sets both to false
_paused : boolean ;
_halted : boolean = true ;
_exited : boolean = true ;
private manualTickEnabled : boolean ;
_nextTick : boolean ;
canDoFallbackMode : boolean = false ;
// helper objects
2021-08-25 22:30:06 +02:00
private animationFrameHandle : any ;
2021-02-18 22:29:23 +01:00
private attachedCanvas : HTMLCanvasElement ;
canvas : HTMLCanvasElement ;
private blackframeCanvas : HTMLCanvasElement ;
private context : CanvasRenderingContext2D ;
private blackframeContext : CanvasRenderingContext2D ;
private canvasScaleFactor : number ;
private detectionTimeoutEventCount : number ;
canvasImageDataRowLength : number ;
private noLetterboxCanvasReset : boolean ;
private detectedAr : any ;
private canvasDrawWindowHOffset : number ;
private sampleCols_current : number ;
2021-08-25 22:30:06 +02:00
private timers = {
nextFrameCheckTime : Date.now ( )
}
private status = {
lastVideoStatus : VideoPlaybackState.Playing
}
2018-05-09 00:34:22 +02:00
2021-08-25 22:30:06 +02:00
//#region debug variables
private performanceConfig = {
sampleCountForAverages : 32
}
private performance = {
animationFrame : {
lastTime : 0 ,
currentIndex : 0 ,
sampleTime : [ ]
} ,
drawImage : {
currentIndex : 0 ,
sampleTime : [ ] ,
} ,
getImageData : {
currentIndex : 0 ,
sampleTime : [ ] ,
} ,
aard : {
currentIndex : 0 ,
sampleTime : [ ] ,
}
} ;
//#endregion
2021-08-25 20:39:27 +02:00
//#region getters
get defaultAr() {
const ratio = this . video . videoWidth / this . video . videoHeight ;
if ( isNaN ( ratio ) ) {
return undefined ;
}
return ratio ;
}
//#endregion getters
2021-08-25 22:30:06 +02:00
//#region debug getters
/ * *
* We get one animation frame per this many ms . This means that our autodetection
* stuff must run in less than this many ms . This valuz is averaged out over multiple
* samples for better accuracy .
*
* Returns value in ms .
*
* A very important caveat : if autodetection takes up too much time , it WILL artificially
* increase time budget . Therefore , you should use ( and firstly even implement ) getTimeBudget ( )
* that turns off autodetection for a second or so to gather accurate timing info .
* /
get eyeballedTimeBudget() {
let sum ;
for ( let i = 0 ; i < this . performance . animationFrame . sampleTime . length ; i ++ ) {
sum += this . performance . animationFrame . sampleTime [ i ] ;
}
return sum / this . performance . animationFrame . sampleTime . length ;
}
/ * *
* Converts time budget ( eyeballed ) into actual framerate . Since eyeballed time budget rises
* if our autodetection takes too long , it ' s still good enough for calculating framerate
* /
get fps() {
return 1000 / this . eyeballedTimeBudget ;
}
//#endregion
2021-08-25 20:39:27 +02:00
//#region lifecycle
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
2018-05-14 20:39:15 +02:00
this . sampleCols = [ ] ;
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-16 01:19:29 +01:00
// we can tick manually, for debugging
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
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
}
2021-02-18 22:29:23 +01:00
setup ( cwidth? : number , cheight? : number ) {
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
//
2021-02-18 22:29:23 +01:00
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
//
2021-02-18 22:38:32 +01:00
let ncol = this . settings . active . arDetect . sampling . staticCols ;
let nrow = this . settings . active . arDetect . sampling . staticRows ;
2018-05-09 00:03:22 +02:00
2021-02-18 22:38:32 +01:00
let colSpacing = this . canvas . width / ncol ;
let rowSpacing = ( this . canvas . height << 2 ) / nrow ;
2019-02-15 00:00:22 +01:00
this . sampleLines = [ ] ;
this . sampleCols = [ ] ;
2018-05-15 20:36:22 +02:00
2021-02-18 22:38:32 +01:00
for ( let i = 0 ; i < ncol ; i ++ ) {
2019-02-15 00:00:22 +01:00
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
}
2021-02-18 22:38:32 +01:00
for ( let i = 0 ; i < nrow ; i ++ ) {
2019-02-15 00:00:22 +01:00
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 {
2021-02-18 22:29:23 +01:00
( this . blackframeContext as any ) . drawWindow ( window , 0 , 0 , this . blackframeCanvas . width , this . blackframeCanvas . height , "rgba(0,0,128,1)" ) ;
2019-02-15 00:00:22 +01:00
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
2021-08-25 20:39:27 +02:00
this . conf . resizer . setLastAr ( { type : AspectRatioType . Automatic , ratio : this.defaultAr } ) ;
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 ( ) ) {
2021-08-26 00:37:56 +02:00
// this.main();
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
}
2021-08-26 00:45:12 +02:00
destroy ( ) {
2021-08-25 20:39:27 +02:00
this . logger . log ( 'info' , 'init' , ` %c[ArDetect::destroy] <@ ${ this . arid } > Destroying aard. ` , _ard_console_stop ) ;
// this.debugCanvas.destroy();
2021-08-25 23:13:02 +02:00
this . halt ( ) ;
2021-08-25 20:39:27 +02:00
}
//#endregion lifecycle
//#region AARD control
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
2021-02-08 23:04:54 +01:00
if ( this . conf . resizer . lastAr . type === AspectRatioType . Automatic ) {
2019-02-16 01:19:29 +01:00
// ensure first autodetection will run in any case
2021-08-25 20:39:27 +02:00
this . conf . resizer . setLastAr ( { type : AspectRatioType . Automatic , ratio : this.defaultAr } ) ;
2019-02-16 01:19:29 +01:00
}
2019-02-15 00:00:22 +01:00
2021-08-25 23:13:02 +02:00
this . _paused = false ;
this . _halted = false ;
this . _paused = false ;
// start autodetection
2021-08-26 00:37:56 +02:00
this . startLoop ( ) ;
// 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.
}
startLoop() {
2021-08-25 23:13:02 +02:00
if ( this . animationFrameHandle ) {
window . cancelAnimationFrame ( this . animationFrameHandle ) ;
}
this . animationFrameHandle = window . requestAnimationFrame ( ( ts ) = > this . animationFrameBootstrap ( ts ) ) ;
}
2021-08-26 00:37:56 +02:00
2021-08-25 23:13:02 +02:00
stop() {
if ( this . animationFrameHandle ) {
window . cancelAnimationFrame ( this . animationFrameHandle ) ;
}
this . logger . log ( 'info' , 'debug' , ` "%c[ArDetect::stop] <@ ${ this . arid } > Stopping AnimationFrame loop. ` , _ard_console_stop ) ;
2018-05-08 23:35:16 +02:00
}
2018-07-11 23:13:40 +02:00
unpause() {
2021-08-26 00:37:56 +02:00
this . startLoop ( ) ;
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 ;
}
2021-08-25 23:13:02 +02:00
this . stop ( ) ;
2018-07-11 23:13:40 +02:00
}
2021-08-25 23:13:02 +02:00
halt ( ) {
this . logger . log ( 'info' , 'debug' , ` "%c[ArDetect::stop] <@ ${ this . arid } > Halting automatic aspect ratio detection ` , _ard_console_stop ) ;
this . 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
}
2021-08-25 20:39:27 +02:00
setManualTick ( manualTick ) {
this . manualTickEnabled = manualTick ;
}
2019-02-16 01:19:29 +01:00
2021-08-25 20:39:27 +02:00
tick() {
this . _nextTick = true ;
}
//#endregion
2019-02-15 00:00:22 +01:00
2021-08-25 20:39:27 +02:00
//#region helper functions (general)
2019-02-15 00:00:22 +01:00
2021-08-25 20:39:27 +02:00
isRunning ( ) {
return ! ( this . _halted || this . _paused || this . _exited ) ;
2019-02-15 00:00:22 +01:00
}
2021-08-25 22:30:06 +02:00
private getVideoPlaybackState ( ) : VideoPlaybackState {
try {
if ( this . video . ended ) {
return VideoPlaybackState . Ended ;
} else if ( this . video . paused ) {
return VideoPlaybackState . Paused ;
} else if ( this . video . error ) {
return VideoPlaybackState . Error ;
} else {
return VideoPlaybackState . Playing ;
}
} catch ( e ) {
this . logger . log ( 'warn' , 'debug' , ` [ArDetect::getVideoPlaybackState] There was an error while determining video playback state. ` , e ) ;
return VideoPlaybackState . Error ;
}
}
/ * *
* Checks whether conditions for granting a frame check are fulfilled
* @returns
* /
private canTriggerFrameCheck() {
2019-02-15 00:00:22 +01:00
if ( this . _paused ) {
return false ;
}
2021-08-25 22:30:06 +02:00
// if video was paused & we know that we already checked that frame,
// we will not check it again.
const videoState = this . getVideoPlaybackState ( ) ;
if ( videoState !== VideoPlaybackState . Playing ) {
if ( this . status . lastVideoStatus === videoState ) {
return false ;
}
2019-02-15 00:00:22 +01:00
}
2021-08-25 22:30:06 +02:00
this . status . lastVideoStatus = videoState ;
2021-08-25 23:13:02 +02:00
if ( Date . now ( ) < this . timers . nextFrameCheckTime ) {
return false ;
}
2021-08-25 22:30:06 +02:00
this . timers . nextFrameCheckTime = Date . now ( ) + this . settings . active . arDetect . timers . playing ;
2021-08-25 23:13:02 +02:00
return true ;
2019-02-15 00:00:22 +01:00
}
2021-08-25 22:30:06 +02:00
private scheduleInitRestart ( timeout? : number , force_reset? : boolean ) {
2018-05-20 23:17:09 +02:00
if ( ! timeout ) {
timeout = 100 ;
}
// don't allow more than 1 instance
if ( this . setupTimer ) {
clearTimeout ( this . setupTimer ) ;
}
2021-02-18 22:38:32 +01:00
let ths = this ;
2018-05-20 23:17:09 +02:00
this . setupTimer = setTimeout ( function ( ) {
ths . setupTimer = null ;
try {
2021-08-26 00:37:56 +02:00
ths . start ( ) ;
2019-07-16 22:46:16 +02:00
} 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
) ;
}
2021-08-25 22:30:06 +02:00
private attachCanvas ( canvas ) {
2018-05-09 00:34:22 +02:00
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 ) ;
}
2021-08-25 22:30:06 +02:00
private 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
2021-08-25 22:30:06 +02:00
/ * *
* Adds execution time sample for performance metrics
* @param performanceObject
* @param executionTime
* /
private addPerformanceTimeMeasure ( performanceObject , executionTime ) {
performanceObject . sampleTime [ performanceObject . currentIndex ] = executionTime ;
performanceObject . currentIndex = ( performanceObject . currentIndex + 1 ) % this . performanceConfig . sampleCountForAverages ;
2018-05-09 00:58:50 +02:00
}
2021-08-25 22:30:06 +02:00
2018-05-09 00:34:22 +02:00
//#endregion
2021-08-25 22:30:06 +02:00
//#region helper functions (performance measurements)
/ * *
* Returns time ultrawidify spends on certain aspects of autodetection .
*
* The returned object contains the following :
*
* eyeballedTimeBudget — a very inaccurate time budget
* fps — framerate at which we run
* aardTime — time spent on average frameCheck loop .
* It ' s a nearly useless metric , because
* frameCheck can exit very early .
* drawWindowTime — how much time browser spends on executing
* drawWindow ( ) calls .
* getImageData — how much time browser spends on executing
* getImageData ( ) calls .
*
* Most of these are on "per frame" basis and averaged .
* /
getTimings() {
let drawWindowTime = 0 , getImageDataTime = 0 , aardTime = 0 ;
// drawImage and getImageData are of same length and use same everything
for ( let i = 0 ; i < this . performance . drawImage . sampleTime . length ; i ++ ) {
drawWindowTime += this . performance . drawImage . sampleTime [ i ] ? ? 0 ;
getImageDataTime += this . performance . getImageData . sampleTime [ i ] ? ? 0 ;
}
drawWindowTime /= this . performance . drawImage . sampleTime . length ;
getImageDataTime /= this . performance . getImageData . sampleTime . length ;
return {
eyeballedTimeBudget : this.eyeballedTimeBudget ,
fps : this.fps ,
drawWindowTime ,
getImageDataTime
}
}
//#endregion
2021-08-25 20:39:27 +02:00
2021-08-25 22:30:06 +02:00
/ * *
* This is the "main loop" for aspect ratio autodetection
* /
2021-09-15 00:54:23 +02:00
private async animationFrameBootstrap ( timestamp : number ) {
2021-08-25 22:30:06 +02:00
// do timekeeping first
2021-09-15 00:54:23 +02:00
this . addPerformanceTimeMeasure ( this . performance . animationFrame , timestamp - this . performance . animationFrame . lastTime ) ;
2021-08-25 22:30:06 +02:00
this . performance . animationFrame . lastTime = timestamp ;
2021-08-25 20:39:27 +02:00
2021-08-25 22:30:06 +02:00
// trigger frame check, if we're allowed to
if ( ( ! this . manualTickEnabled && this . canTriggerFrameCheck ( ) ) || this . _nextTick ) {
this . _nextTick = false ;
2021-08-25 20:39:27 +02:00
2021-08-25 22:30:06 +02:00
try {
const startTime = performance . now ( ) ;
2021-09-15 00:54:23 +02:00
await this . frameCheck ( ) ;
2021-08-25 22:30:06 +02:00
this . addPerformanceTimeMeasure ( this . performance . aard , performance . now ( ) - startTime ) ;
} catch ( e ) {
this . logger . log ( 'error' , 'debug' , ` %c[ArDetect::main] <@ ${ this . arid } > Frame check failed: ` , "color: #000, background: #f00" , e ) ;
2021-08-25 20:39:27 +02:00
}
}
2021-08-25 22:30:06 +02:00
if ( this && ! this . _halted && ! this . _paused ) {
2021-08-25 23:13:02 +02:00
this . animationFrameHandle = window . requestAnimationFrame ( ( ts ) = > this . animationFrameBootstrap ( ts ) ) ;
2021-08-25 22:30:06 +02:00
} else if ( this . _halted ) {
this . logger . log ( 'info' , 'debug' , ` %c[ArDetect::main] <@ ${ this . arid } > Main autodetection loop exited. Halted? ${ this . _halted } ` , _ard_console_stop ) ;
this . _exited = true ;
} else {
this . logger . log ( 'info' , 'debug' , ` [ArDetect::main] <@ ${ this . arid } > Not renewing animation frame for some reason. Paused? ${ this . _paused } ; Halted?: ${ this . _halted } , Exited?: ${ this . _exited } ` ) ;
}
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]
2021-02-18 22:38:32 +01:00
let vbr = this . video . getBoundingClientRect ( ) ;
2018-07-11 23:13:40 +02:00
zoomFactor = vbr . height / this . video . clientHeight ;
letterbox += vbr . height - this . video . clientHeight ;
2021-02-18 22:38:32 +01:00
let 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:
2021-01-12 23:19:04 +01:00
let lastAr = this . conf . resizer . getLastAr ( ) ;
2021-02-08 23:04:54 +01:00
if ( lastAr . type === AspectRatioType . 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.
2021-02-18 22:29:23 +01:00
let arDiff = trueAr - lastAr . ratio ;
2018-05-09 00:34:22 +02:00
if ( arDiff < 0 )
arDiff = - arDiff ;
2021-01-12 23:19:04 +01:00
const arDiff_percent = arDiff / trueAr ;
2018-05-09 00:34:22 +02:00
// ali je sprememba v mejah dovoljenega? Če da -> fertik
// is ar variance within acceptable levels? If yes -> we done
2021-02-18 22:29:23 +01: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 . ratio , "current ar" , trueAr , "arDiff (absolute):" , arDiff , "ar diff (relative to new ar)" , arDiff_percent ) ;
2019-07-16 22:46:16 +02:00
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
2021-02-18 22:29:23 +01:00
this . conf . resizer . updateAr ( { type : AspectRatioType . Automatic , ratio : trueAr } ) ;
2018-05-09 00:34:22 +02:00
}
2019-10-29 18:15:46 +01:00
clearImageData ( id ) {
2021-02-18 22:29:23 +01:00
if ( ( ArrayBuffer as any ) . transfer ) {
( ArrayBuffer as any ) . transfer ( id , 0 ) ;
2019-10-29 18:15:46 +01:00
}
id = undefined ;
}
2021-09-14 19:12:54 +02:00
async frameCheck ( ) {
2021-09-15 00:54:23 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::processAr] <@ ${ this . arid } > Starting frame check. ` ) ;
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
2021-08-25 22:30:06 +02:00
let startTime ;
let partialDrawImageTime = 0 ;
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 {
2021-08-25 22:30:06 +02:00
startTime = performance . now ( ) ;
2021-09-14 19:12:54 +02:00
// do it in ghetto async. This way, other javascript function should be able to
// get a chance to do something _before_ we process our data
await new Promise < void > (
resolve = > {
this . blackframeContext . drawImage ( this . video , 0 , 0 , this . blackframeCanvas . width , this . blackframeCanvas . height ) ;
resolve ( ) ;
}
) ;
2021-08-25 22:30:06 +02:00
partialDrawImageTime += performance . now ( ) - startTime ;
2020-12-07 00:09:08 +01:00
2019-02-15 00:00:22 +01:00
this . fallbackMode = false ;
2021-08-25 22:30:06 +02:00
// If we detected DRM and if we're here, this means we're also using Google Chrome.
// And if we're here while DRM is detected, we know we'll be looking at black frames.
// We won't be able to do anything useful, therefore we're just gonna call it quits.
if ( this . conf . hasDrm ) {
2021-08-26 00:37:56 +02:00
this . logger . log ( 'info' , 'debug' , 'we have drm, doing nothing.' , this . conf . hasDrm ) ;
2021-08-25 22:30:06 +02:00
return ;
}
2019-02-15 00:00:22 +01:00
} 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
if ( ! this . canvasReadyForDrawWindow ( ) ) {
// this means canvas needs to be resized, so we'll just re-run setup with all those new parameters
2021-08-25 23:13:02 +02:00
this . halt ( ) ;
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
2021-02-08 23:04:54 +01:00
if ( this . conf . resizer . videoAlignment === VideoAlignmentType . Center ) {
2019-02-15 00:00:22 +01:00
this . canvasDrawWindowHOffset = Math . round ( ( window . innerWidth - newCanvasWidth ) * 0.5 ) ;
2021-02-08 23:04:54 +01:00
} else if ( this . conf . resizer . videoAlignment === VideoAlignmentType . Left ) {
2019-02-15 00:00:22 +01:00
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 ;
2021-08-25 22:30:06 +02:00
startTime = performance . now ( ) ;
2019-02-15 00:00:22 +01:00
try {
2021-08-25 22:30:06 +02:00
( this . context as any ) . drawWindow ( window , this . canvasDrawWindowHOffset , 0 , this . canvas . width , this . canvas . height ) ;
2019-02-15 00:00:22 +01:00
} 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:
2021-09-15 00:54:23 +02:00
await new Promise < void > ( resolve = > {
2021-09-14 19:12:54 +02:00
this . blackframeContext . drawImage ( this . canvas , this . blackframeCanvas . width , this . blackframeCanvas . height ) ;
2021-09-15 00:54:23 +02:00
resolve ( ) ;
2021-09-14 19:12:54 +02:00
} ) ;
2021-08-25 22:30:06 +02:00
partialDrawImageTime += performance . now ( ) - startTime ;
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-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 ) {
2021-08-25 22:30:06 +02:00
startTime = performance . now ( ) ;
2021-09-15 00:54:23 +02:00
await new Promise < void > ( resolve = > {
2021-09-14 19:12:54 +02:00
this . context . drawImage ( this . video , 0 , 0 , this . canvas . width , this . canvas . height ) ;
2021-09-15 00:54:23 +02:00
resolve ( ) ;
2021-09-14 19:12:54 +02:00
} )
2021-08-25 22:30:06 +02:00
partialDrawImageTime += performance . now ( ) - startTime ;
2018-05-09 00:58:50 +02:00
}
2021-08-25 22:30:06 +02:00
this . addPerformanceTimeMeasure ( this . performance . drawImage , partialDrawImageTime ) ;
startTime = performance . now ( ) ;
2019-02-15 00:00:22 +01:00
const imageData = this . context . getImageData ( 0 , 0 , this . canvas . width , this . canvas . height ) . data ;
2021-08-25 22:30:06 +02:00
this . addPerformanceTimeMeasure ( this . performance . getImageData , performance . now ( ) - startTime ) ;
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.
2021-08-25 20:39:27 +02:00
this . conf . resizer . updateAr ( { type : AspectRatioType . Automatic , ratio : this.defaultAr } ) ;
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
2021-08-26 00:37:56 +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 ) {
2021-02-18 22:35:58 +01:00
sampleCols . concat ( guardLineOut . offenders ) . sort (
( a : number , b : number ) = > a - b
) ;
2019-02-15 00:00:22 +01:00
}
// 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 ) {
2021-09-15 00:54:23 +02:00
this . logger . log ( 'warn' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] <@ ${ this . arid } > We are in fallback mode and blackbar failed. Reverting to initial aspect ratio. ` ) ;
2021-08-25 20:39:27 +02:00
this . conf . resizer . setAr ( { type : AspectRatioType . Automatic , ratio : this.defaultAr } ) ;
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 ;
2021-09-15 00:54:23 +02:00
}
2018-05-09 00:58:50 +02:00
// 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 ) {
2021-03-06 04:01:47 +01:00
if ( this . edgeDetector . findBars ( imageData , null , EdgeDetectPrimaryDirection . Horizontal ) . status === 'ar_known' ) {
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. ` ) ;
2021-08-25 20:39:27 +02:00
this . conf . resizer . setAr ( { type : AspectRatioType . Automatic , ratio : this.defaultAr } ) ;
2018-07-11 00:01:44 +02:00
this . guardLine . reset ( ) ;
2021-09-15 00:54:23 +02:00
} else {
this . logger . log ( 'info' , 'arDetect_verbose' , ` [ArDetect::frameCheck] Guardline failed, blackbar didn't, and we got pillarbox. Doing nothing. ` ) ;
}
2018-03-04 23:07:11 +01:00
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
2021-03-06 04:01:47 +01:00
let 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
2021-03-06 04:01:47 +01:00
if ( edgePost . status !== EdgeStatus . ARKnown ) {
2019-02-15 00:00:22 +01:00
// rob ni bil zaznan, zato ne naredimo ničesar.
// no edge was detected. Let's leave things as they were
2021-03-06 04:01:47 +01:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] Edge wasn't detected with findBars ` , "color: #fa3" , edgePost , "EdgeStatus.AR_KNOWN:" , EdgeStatus . ARKnown ) ;
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
2021-02-18 22:38:32 +01:00
let newAr = this . calculateArFromEdges ( edgePost ) ;
2019-02-15 00:00:22 +01:00
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 {
2021-02-18 22:29:23 +01:00
this . guardLine . reset ( ) ;
2020-06-01 23:52:29 +02:00
} 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)
//
2021-08-25 20:39:27 +02:00
// this.conf.resizer.setAr({type: AspectRatioType.Automatic, ratio: this.defaultAr});
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 ;
2021-02-18 22:29:23 +01:00
const blackTreshold = this . blackLevel + this . settings . active . arDetect . blackbar . frameThreshold ;
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 ) ;
2021-02-18 22:29:23 +01:00
let r : number , c : number ;
2019-02-15 00:00:22 +01:00
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
2021-08-26 00:37:56 +02:00
/ * *
* Does a quick test to see if the aspect ratio is correct
* Returns 'true' if there ' s a chance of letterbox existing , false if not .
* @param imageData
* @param sampleCols
* @returns
* /
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
2021-02-18 22:38:32 +01:00
for ( let i = 0 ; i < sampleCols . length ; ++ i ) {
2019-02-15 00:00:22 +01:00
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
) ;
}
2021-08-26 00:37:56 +02:00
if ( currentMin < this . blackLevel ) {
2019-02-15 00:00:22 +01:00
this . blackLevel = currentMin
2021-08-26 00:37:56 +02:00
}
2019-02-15 00:00:22 +01:00
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
2021-02-18 22:38:32 +01:00
let _ard_console_stop = "background: #000; color: #f41" ;
let _ard_console_start = "background: #000; color: #00c399" ;
let _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 ;