import Reader from "./../core/Reader" ; import BinaryBitmap from './../core/BinaryBitmap' ; import HybridBinarizer from './../core/common/HybridBinarizer' ; import Result from './../core/Result' ; import Exception from './../core/Exception' ; import HTMLCanvasElementLuminanceSource from './HTMLCanvasElementLuminanceSource' ; import VideoInputDevice from './VideoInputDevice' ; export class export default class BrowserCodeReader { private videoElement : HTMLVideoElement ; private imageElement : HTMLImageElement ; private canvasElement : HTMLCanvasElement ; private canvasElementContext : CanvasRenderingContext2D ; private timeoutHandler : number ; private stream : MediaStream ; private videoPlayEndedEventListener : EventListener ; private videoPlayingEventListener : EventListener ; private imageLoadedEventListener : EventListener ; param { Reader } reader param { number } timeBetweenScansMillis memberOf public constructor ( private reader , private timeBetweenScansMillis = 500 ) { } returns { Promise < VideoInputDevice [ ] > } memberOf public getVideoInputDevices ( ) { return new Promise < VideoInputDevice [ ] > ( ( resolve , reject ) => { navigator . mediaDevices . enumerateDevices ( ) . then ( ( devices ) => { const sources = new Array < VideoInputDevice > ( ) ; let c = 0 ; for ( let i = 0 , length = devices . length ; i != length ; i ++ ) { const device = devices [ i ] ; if ( device . kind === 'videoinput' ) { sources . push ( new VideoInputDevice ( device . deviceId , device . label || `Video source ${ c }` ) ) ; c ++ ; } } resolve ( sources ) ; } ) . catch ( ( err ) => { reject ( err ) ; } ) ; } ) ; } param { string } deviceId param { ( string | HTMLVideoElement ) } videoElement returns { Promise < Result > } memberOf public decodeFromInputVideoDevice ( deviceId ? , videoElement ? ) { this . reset ( ) ; this . prepareVideoElement ( videoElement ) ; let constraints ; if ( undefined === deviceId ) { constraints = { video : { facingMode : "environment" } } ; } else { constraints = { video : { deviceId : { exact : deviceId } } } ; } const me = this ; return new Promise < Result > ( ( resolve , reject ) => { navigator . mediaDevices . getUserMedia ( constraints ) . then ( ( stream ) => { me . stream = stream ; me . videoElement . srcObject = stream ; me . videoPlayingEventListener = ( ) => { me . decodeOnceWithDelay ( resolve , reject ) ; } ; me . videoElement . addEventListener ( 'playing' , me . videoPlayingEventListener ) ; me . videoElement . play ( ) ; } ) . catch ( ( error ) => { reject ( error ) ; } ) ; } ) ; } param { string } videoUrl param { ( string | HTMLVideoElement ) } videoElement returns { Promise < Result > } memberOf public decodeFromVideoSource ( videoUrl , videoElement ? ) { this . reset ( ) ; this . prepareVideoElement ( videoElement ) ; const me = this ; return new Promise < Result > ( ( resolve , reject ) => { me . videoPlayEndedEventListener = ( ) => { me . stop ( ) ; reject ( new Exception ( Exception . NotFoundException ) ) ; } ; me . videoElement . addEventListener ( 'ended' , me . videoPlayEndedEventListener ) ; me . videoPlayingEventListener = ( ) => { me . decodeOnceWithDelay ( resolve , reject ) ; } ; me . videoElement . addEventListener ( 'playing' , me . videoPlayingEventListener ) ; me . videoElement . setAttribute ( 'autoplay' , 'true' ) ; me . videoElement . setAttribute ( 'src' , videoUrl ) ; } ) ; } private prepareVideoElement ( videoElement ? ) { if ( undefined === videoElement ) { this . videoElement = document . createElement ( 'video' ) ; this . videoElement . width = 200 ; this . videoElement . height = 200 ; } else if ( typeof videoElement === 'string' ) { this . videoElement = < HTMLVideoElement > this . getMediaElement ( videoElement , 'video' ) ; } else { this . videoElement = videoElement ; } this . videoElement . setAttribute ( "autoplay" , "true" ) ; this . videoElement . setAttribute ( "muted" , "true" ) ; this . videoElement . setAttribute ( "playsinline" , "true" ) ; this . videoElement . setAttribute ( "autofocus" , "true" ) ; } private getMediaElement ( mediaElementId , type ) { const mediaElement = document . getElementById ( mediaElementId ) ; if ( null === mediaElement ) { throw new Exception ( Exception . ArgumentException , `element with id '${ mediaElementId }' not found` ) ; } if ( mediaElement . nodeName . toLowerCase ( ) !== type . toLowerCase ( ) ) { console . log ( mediaElement . nodeName ) ; throw new Exception ( Exception . ArgumentException , `element with id '${ mediaElementId }' must be an ${ type } element` ) ; } return mediaElement ; } param { ( string | HTMLImageElement ) } imageElement param { string } imageUrl returns { Promise < Result > } memberOf public decodeFromImage ( imageElement ? , imageUrl ? ) { this . reset ( ) ; if ( undefined === imageElement && undefined === imageUrl ) { throw new Exception ( Exception . ArgumentException , 'either imageElement with a src set or an url must be provided' ) ; } this . prepareImageElement ( imageElement ) ; const me = this ; return new Promise < Result > ( ( resolve , reject ) => { if ( undefined !== imageUrl ) { me . imageLoadedEventListener = ( ) => { me . decodeOnce ( resolve , reject , false , true ) ; } ; me . imageElement . addEventListener ( 'load' , me . imageLoadedEventListener ) ; me . imageElement . src = imageUrl ; } else if ( this . isImageLoaded ( this . imageElement ) ) { me . decodeOnce ( resolve , reject , false , true ) ; } else { throw new Exception ( Exception . ArgumentException , `either src or a loaded img should be provided` ) ; } } ) ; } private isImageLoaded ( img ) { if ( ! img . complete ) { return false ; } if ( img . naturalWidth === 0 ) { return false ; } return true ; } private prepareImageElement ( imageElement ? ) { if ( undefined === imageElement ) { this . imageElement = document . createElement ( 'img' ) ; this . imageElement . width = 200 ; this . imageElement . height = 200 ; } else if ( typeof imageElement === 'string' ) { this . imageElement = < HTMLImageElement > this . getMediaElement ( imageElement , 'img' ) ; } else { this . imageElement = imageElement ; } } private decodeOnceWithDelay ( resolve , reject ) { this . timeoutHandler = window . setTimeout ( this . decodeOnce . bind ( this , resolve , reject ) , this . timeBetweenScansMillis ) ; } private decodeOnce ( resolve , reject , retryIfNotFound = true , retryIfChecksumOrFormatError = true ) { if ( undefined === this . canvasElementContext ) { this . prepareCaptureCanvas ( ) ; } this . canvasElementContext . drawImage ( this . videoElement || this . imageElement , 0 , 0 ) ; const luminanceSource = new HTMLCanvasElementLuminanceSource ( this . canvasElement ) ; const binaryBitmap = new BinaryBitmap ( new HybridBinarizer ( luminanceSource ) ) ; try { const result = this . readerDecode ( binaryBitmap ) ; resolve ( result ) ; } catch ( re ) { console . log ( retryIfChecksumOrFormatError , re ) ; if ( retryIfNotFound && Exception . isOfType ( re , Exception . NotFoundException ) ) { console . log ( 'not found, trying again...' ) ; this . decodeOnceWithDelay ( resolve , reject ) ; } else if ( retryIfChecksumOrFormatError && ( Exception . isOfType ( re , Exception . ChecksumException ) || Exception . isOfType ( re , Exception . FormatException ) ) ) { console . log ( 'checksum or format error, trying again...' , re ) ; this . decodeOnceWithDelay ( resolve , reject ) ; } else { reject ( re ) ; } } } protected readerDecode ( binaryBitmap ) { return this . reader . decode ( binaryBitmap ) ; } private prepareCaptureCanvas ( ) { const canvasElement = document . createElement ( 'canvas' ) ; let width , height ; if ( undefined !== this . videoElement ) { width = this . videoElement . videoWidth ; height = this . videoElement . videoHeight ; } else { width = this . imageElement . naturalWidth || this . imageElement . width ; height = this . imageElement . naturalHeight || this . imageElement . height ; } canvasElement . style . width = `${ width }px` ; canvasElement . style . height = `${ height }px` ; canvasElement . width = width ; canvasElement . height = height ; this . canvasElement = canvasElement ; this . canvasElementContext = canvasElement . getContext ( '2d' ) ; } private stop ( ) { if ( undefined !== this . timeoutHandler ) { window . clearTimeout ( this . timeoutHandler ) ; this . timeoutHandler = undefined ; } if ( undefined !== this . stream ) { this . stream . getTracks ( ) [ 0 ] . stop ( ) ; this . stream = undefined ; } } memberOf public reset ( ) { this . stop ( ) ; if ( undefined !== this . videoPlayEndedEventListener && undefined !== this . videoElement ) { this . videoElement . removeEventListener ( 'ended' , this . videoPlayEndedEventListener ) ; } if ( undefined !== this . videoPlayingEventListener && undefined !== this . videoElement ) { this . videoElement . removeEventListener ( 'playing' , this . videoPlayingEventListener ) ; } if ( undefined !== this . videoElement ) { this . videoElement . srcObject = undefined ; this . videoElement . removeAttribute ( 'src' ) ; this . videoElement = undefined ; } if ( undefined !== this . videoPlayEndedEventListener && undefined !== this . imageElement ) { this . imageElement . removeEventListener ( 'load' , this . imageLoadedEventListener ) ; } if ( undefined !== this . imageElement ) { this . imageElement . src = undefined ; this . imageElement . removeAttribute ( 'src' ) ; this . imageElement = undefined ; } this . canvasElementContext = undefined ; this . canvasElement = undefined ; } }