import ArgumentException from '../core/ArgumentException' ; import BinaryBitmap from '../core/BinaryBitmap' ; import ChecksumException from '../core/ChecksumException' ; import HybridBinarizer from '../core/common/HybridBinarizer' ; import DecodeHintType from '../core/DecodeHintType' ; import FormatException from '../core/FormatException' ; import NotFoundException from '../core/NotFoundException' ; import Reader from '../core/Reader' ; import Result from '../core/Result' ; import { DecodeContinuouslyCallback } from './DecodeContinuouslyCallback' ; import { HTMLCanvasElementLuminanceSource } from './HTMLCanvasElementLuminanceSource' ; import { HTMLVisualMediaElement } from './HTMLVisualMediaElement' ; import { VideoInputDevice } from './VideoInputDevice' ; deprecated zxing export class BrowserCodeReader { /**
     * If navigator is present.
     */ public get hasNavigator ( ) { return typeof navigator !== 'undefined' ; } /**
     * If mediaDevices under navigator is supported.
     */ public get isMediaDevicesSuported ( ) { return this . hasNavigator && ! ! navigator . mediaDevices ; } /**
     * If enumerateDevices under navigator is supported.
     */ public get canEnumerateDevices ( ) { return ! ! ( this . isMediaDevicesSuported && navigator . mediaDevices . enumerateDevices ) ; } /**
     * This will break the loop.
     */ private _stopContinuousDecode = false ; /**
     * This will break the loop.
     */ private _stopAsyncDecode = false ; /**
     * Delay time between decode attempts made by the scanner.
     */ protected _timeBetweenDecodingAttempts : number = 0 ; /** Time between two decoding tries in milli seconds. */ get timeBetweenDecodingAttempts ( ) : number { return this . _timeBetweenDecodingAttempts ; } param { number } millis set timeBetweenDecodingAttempts ( millis ) { this . _timeBetweenDecodingAttempts = millis < 0 ? 0 : millis ; } /**
     * The HTML canvas element, used to draw the video or image's frame for decoding.
     */ protected captureCanvas : HTMLCanvasElement ; /**
     * The HTML canvas element context.
     */ protected captureCanvasContext : CanvasRenderingContext2D ; /**
     * The HTML image element, used as a fallback for the video element when decoding.
     */ protected imageElement : HTMLImageElement ; /**
     * Should contain the current registered listener for image loading,
     * used to unregister that listener when needed.
     */ protected imageLoadedListener : EventListener ; /**
     * The stream output from camera.
     */ protected stream : MediaStream ; /**
     * The HTML video element, used to display the camera stream.
     */ protected videoElement : HTMLVideoElement ; /**
     * Should contain the current registered listener for video loaded-metadata,
     * used to unregister that listener when needed.
     */ protected videoCanPlayListener : EventListener ; /**
     * Should contain the current registered listener for video play-ended,
     * used to unregister that listener when needed.
     */ protected videoEndedListener : EventListener ; /**
     * Should contain the current registered listener for video playing,
     * used to unregister that listener when needed.
     */ protected videoPlayingEventListener : EventListener ; /**
     * Sets the hints.
     */ set hints ( hints ) { this . _hints = hints || null ; } /**
     * Sets the hints.
     */ get hints ( ) : Map < DecodeHintType , any > { return this . _hints ; } param { Reader } reader param { number } timeBetweenScansMillis memberOf public constructor ( protected readonly reader , protected timeBetweenScansMillis = 500 , protected _hints ? ) { } /**
     * Lists all the available video input devices.
     */ public async listVideoInputDevices ( ) { if ( ! this . hasNavigator ) { throw new Error ( 'Can\'t enumerate devices, navigator is not present.' ) ; } if ( ! this . canEnumerateDevices ) { throw new Error ( 'Can\'t enumerate devices, method not supported.' ) ; } const devices = await navigator . mediaDevices . enumerateDevices ( ) ; const videoDevices = [ ] ; for ( const device of devices ) { const kind = < string > device . kind === 'video' ? 'videoinput' : device . kind ; if ( kind !== 'videoinput' ) { continue ; } const deviceId = device . deviceId || ( < any > device ) . id ; const label = device . label || `Video device ${ videoDevices . length + 1 }` ; const groupId = device . groupId ; const videoDevice = < MediaDeviceInfo > { deviceId , label , kind , groupId } ; videoDevices . push ( videoDevice ) ; } return videoDevices ; } returns { Promise < VideoInputDevice [ ] > } memberOf deprecated public async getVideoInputDevices ( ) { const devices = await this . listVideoInputDevices ( ) ; return devices . map ( d => new VideoInputDevice ( d . deviceId , d . label ) ) ; } /**
     * Let's you find a device using it's Id.
     */ public async findDeviceById ( deviceId ) { const devices = await this . listVideoInputDevices ( ) ; if ( ! devices ) { return null ; } return devices . find ( x => x . deviceId === deviceId ) ; } param deviceId param video returns memberOf deprecated public async decodeFromInputVideoDevice ( deviceId ? , videoSource ? ) { return await this . decodeOnceFromVideoDevice ( deviceId , videoSource ) ; } param deviceId param video returns memberOf public async decodeOnceFromVideoDevice ( deviceId ? , videoSource ? ) { this . reset ( ) ; let videoConstraints ; if ( ! deviceId ) { videoConstraints = { facingMode : 'environment' } ; } else { videoConstraints = { deviceId : { exact : deviceId } } ; } const constraints = { video : videoConstraints } ; return await this . decodeOnceFromConstraints ( constraints , videoSource ) ; } param constraints param video returns memberOf public async decodeOnceFromConstraints ( constraints , videoSource ? ) { const stream = await navigator . mediaDevices . getUserMedia ( constraints ) ; return await this . decodeOnceFromStream ( stream , videoSource ) ; } param { MediaStream } constraints param { string | HTMLVideoElement } video returns { Promise < Result > } memberOf public async decodeOnceFromStream ( stream , videoSource ? ) { this . reset ( ) ; const video = await this . attachStreamToVideo ( stream , videoSource ) ; const result = await this . decodeOnce ( video ) ; return result ; } param { string | null } deviceId param { string | HTMLVideoElement | null } video returns { Promise < void > } memberOf deprecated public async decodeFromInputVideoDeviceContinuously ( deviceId , videoSource , callbackFn ) { return await this . decodeFromVideoDevice ( deviceId , videoSource , callbackFn ) ; } param { string | null } deviceId param { string | HTMLVideoElement | null } video returns { Promise < void > } memberOf public async decodeFromVideoDevice ( deviceId , videoSource , callbackFn ) { let videoConstraints ; if ( ! deviceId ) { videoConstraints = { facingMode : 'environment' } ; } else { videoConstraints = { deviceId : { exact : deviceId } } ; } const constraints = { video : videoConstraints } ; return await this . decodeFromConstraints ( constraints , videoSource , callbackFn ) ; } param { MediaStream } constraints param { string | HTMLVideoElement } video returns { Promise < Result > } memberOf public async decodeFromConstraints ( constraints , videoSource , callbackFn ) { const stream = await navigator . mediaDevices . getUserMedia ( constraints ) ; return await this . decodeFromStream ( stream , videoSource , callbackFn ) ; } param { MediaStream } constraints param { string | HTMLVideoElement } video returns { Promise < Result > } memberOf public async decodeFromStream ( stream , videoSource , callbackFn ) { this . reset ( ) ; const video = await this . attachStreamToVideo ( stream , videoSource ) ; return await this . decodeContinuously ( video , callbackFn ) ; } /**
     * Breaks the decoding loop.
     */ public stopAsyncDecode ( ) { this . _stopAsyncDecode = true ; } /**
     * Breaks the decoding loop.
     */ public stopContinuousDecode ( ) { this . _stopContinuousDecode = true ; } param stream param decodeFn protected async attachStreamToVideo ( stream , videoSource ) { const videoElement = this . prepareVideoElement ( videoSource ) ; this . addVideoSource ( videoElement , stream ) ; this . videoElement = videoElement ; this . stream = stream ; await this . playVideoOnLoadAsync ( videoElement ) ; return videoElement ; } param videoElement protected playVideoOnLoadAsync ( videoElement ) { return new Promise ( ( resolve , reject ) => this . playVideoOnLoad ( videoElement , ( ) => resolve ( ) ) ) ; } param element param callbackFn protected playVideoOnLoad ( element , callbackFn ) { this . videoEndedListener = ( ) => this . stopStreams ( ) ; this . videoCanPlayListener = ( ) => this . tryPlayVideo ( element ) ; element . addEventListener ( 'ended' , this . videoEndedListener ) ; element . addEventListener ( 'canplay' , this . videoCanPlayListener ) ; element . addEventListener ( 'playing' , callbackFn ) ; this . tryPlayVideo ( element ) ; } /**
     * Checks if the given video element is currently playing.
     */ isVideoPlaying ( video ) { return video . currentTime > 0 && ! video . paused && ! video . ended && video . readyState > 2 ; } /**
     * Just tries to play the video and logs any errors.
     * The play call is only made is the video is not already playing.
     */ async tryPlayVideo ( videoElement ) { if ( this . isVideoPlaying ( videoElement ) ) { console . warn ( 'Trying to play video that is already playing.' ) ; return ; } try { await videoElement . play ( ) ; } catch { console . warn ( 'It was not possible to play the video.' ) ; } } /**
     * Searches and validates a media element.
     */ public getMediaElement ( mediaElementId , type ) { const mediaElement = document . getElementById ( mediaElementId ) ; if ( ! mediaElement ) { throw new ArgumentException ( `element with id '${ mediaElementId }' not found` ) ; } if ( mediaElement . nodeName . toLowerCase ( ) !== type . toLowerCase ( ) ) { throw new ArgumentException ( `element with id '${ mediaElementId }' must be an ${ type } element` ) ; } return < HTMLVisualMediaElement > mediaElement ; } param { ( string | HTMLImageElement ) } source param { string } url returns { Promise < Result > } memberOf public decodeFromImage ( source ? , url ? ) { if ( ! source && ! url ) { throw new ArgumentException ( 'either imageElement with a src set or an url must be provided' ) ; } if ( url && ! source ) { return this . decodeFromImageUrl ( url ) ; } return this . decodeFromImageElement ( source ) ; } param { ( string | HTMLImageElement ) } source param { string } url returns { Promise < Result > } memberOf public decodeFromVideo ( source ? , url ? ) { if ( ! source && ! url ) { throw new ArgumentException ( 'Either an element with a src set or an URL must be provided' ) ; } if ( url && ! source ) { return this . decodeFromVideoUrl ( url ) ; } return this . decodeFromVideoElement ( source ) ; } param { ( string | HTMLImageElement ) } source param { string } url returns { Promise < Result > } memberOf experimental public decodeFromVideoContinuously ( source , url , callbackFn ) { if ( undefined === source && undefined === url ) { throw new ArgumentException ( 'Either an element with a src set or an URL must be provided' ) ; } if ( url && ! source ) { return this . decodeFromVideoUrlContinuously ( url , callbackFn ) ; } return this . decodeFromVideoElementContinuously ( source , callbackFn ) ; } /**
     * Decodes something from an image HTML element.
     */ public decodeFromImageElement ( source ) { if ( ! source ) { throw new ArgumentException ( 'An image element must be provided.' ) ; } this . reset ( ) ; const element = this . prepareImageElement ( source ) ; this . imageElement = element ; let task ; if ( this . isImageLoaded ( element ) ) { task = this . decodeOnce ( element , false , true ) ; } else { task = this . _decodeOnLoadImage ( element ) ; } return task ; } /**
     * Decodes something from an image HTML element.
     */ public decodeFromVideoElement ( source ) { const element = this . _decodeFromVideoElementSetup ( source ) ; return this . _decodeOnLoadVideo ( element ) ; } /**
     * Decodes something from an image HTML element.
     */ public decodeFromVideoElementContinuously ( source , callbackFn ) { const element = this . _decodeFromVideoElementSetup ( source ) ; return this . _decodeOnLoadVideoContinuously ( element , callbackFn ) ; } param source private _decodeFromVideoElementSetup ( source ) { if ( ! source ) { throw new ArgumentException ( 'A video element must be provided.' ) ; } this . reset ( ) ; const element = this . prepareVideoElement ( source ) ; this . videoElement = element ; return element ; } /**
     * Decodes an image from a URL.
     */ public decodeFromImageUrl ( url ? ) { if ( ! url ) { throw new ArgumentException ( 'An URL must be provided.' ) ; } this . reset ( ) ; const element = this . prepareImageElement ( ) ; this . imageElement = element ; const decodeTask = this . _decodeOnLoadImage ( element ) ; element . src = url ; return decodeTask ; } /**
     * Decodes an image from a URL.
     */ public decodeFromVideoUrl ( url ) { if ( ! url ) { throw new ArgumentException ( 'An URL must be provided.' ) ; } this . reset ( ) ; const element = this . prepareVideoElement ( ) ; const decodeTask = this . decodeFromVideoElement ( element ) ; element . src = url ; return decodeTask ; } experimental public decodeFromVideoUrlContinuously ( url , callbackFn ) { if ( ! url ) { throw new ArgumentException ( 'An URL must be provided.' ) ; } this . reset ( ) ; const element = this . prepareVideoElement ( ) ; const decodeTask = this . decodeFromVideoElementContinuously ( element , callbackFn ) ; element . src = url ; return decodeTask ; } private _decodeOnLoadImage ( element ) { return new Promise ( ( resolve , reject ) => { this . imageLoadedListener = ( ) => this . decodeOnce ( element , false , true ) . then ( resolve , reject ) ; element . addEventListener ( 'load' , this . imageLoadedListener ) ; } ) ; } private async _decodeOnLoadVideo ( videoElement ) { await this . playVideoOnLoadAsync ( videoElement ) ; return await this . decodeOnce ( videoElement ) ; } private async _decodeOnLoadVideoContinuously ( videoElement , callbackFn ) { await this . playVideoOnLoadAsync ( videoElement ) ; this . decodeContinuously ( videoElement , callbackFn ) ; } public isImageLoaded ( img ) { if ( ! img . complete ) { return false ; } if ( img . naturalWidth === 0 ) { return false ; } return true ; } public prepareImageElement ( imageSource ? ) { let imageElement ; if ( typeof imageSource === 'undefined' ) { imageElement = document . createElement ( 'img' ) ; imageElement . width = 200 ; imageElement . height = 200 ; } if ( typeof imageSource === 'string' ) { imageElement = < HTMLImageElement > this . getMediaElement ( imageSource , 'img' ) ; } if ( imageSource instanceof HTMLImageElement ) { imageElement = imageSource ; } return imageElement ; } param videoSource public prepareVideoElement ( videoSource ? ) { let videoElement ; if ( ! videoSource && typeof document !== 'undefined' ) { videoElement = document . createElement ( 'video' ) ; videoElement . width = 200 ; videoElement . height = 200 ; } if ( typeof videoSource === 'string' ) { videoElement = < HTMLVideoElement > this . getMediaElement ( videoSource , 'video' ) ; } if ( videoSource instanceof HTMLVideoElement ) { videoElement = videoSource ; } videoElement . setAttribute ( 'autoplay' , 'true' ) ; videoElement . setAttribute ( 'muted' , 'true' ) ; videoElement . setAttribute ( 'playsinline' , 'true' ) ; return videoElement ; } /**
     * Tries to decode from the video input until it finds some value.
     */ public decodeOnce ( element , retryIfNotFound = true , retryIfChecksumOrFormatError = true ) { this . _stopAsyncDecode = false ; const loop = ( resolve , reject ) => { if ( this . _stopAsyncDecode ) { reject ( new NotFoundException ( 'Video stream has ended before any code could be detected.' ) ) ; this . _stopAsyncDecode = undefined ; return ; } try { const result = this . decode ( element ) ; resolve ( result ) ; } catch ( e ) { const ifNotFound = retryIfNotFound && e instanceof NotFoundException ; const isChecksumOrFormatError = e instanceof ChecksumException || e instanceof FormatException ; const ifChecksumOrFormat = isChecksumOrFormatError && retryIfChecksumOrFormatError ; if ( ifNotFound || ifChecksumOrFormat ) { return setTimeout ( loop , this . _timeBetweenDecodingAttempts , resolve , reject ) ; } reject ( e ) ; } } ; return new Promise ( ( resolve , reject ) => loop ( resolve , reject ) ) ; } /**
     * Continuously decodes from video input.
     */ public decodeContinuously ( element , callbackFn ) { this . _stopContinuousDecode = false ; const loop = ( ) => { if ( this . _stopContinuousDecode ) { this . _stopContinuousDecode = undefined ; return ; } try { const result = this . decode ( element ) ; callbackFn ( result , null ) ; setTimeout ( loop , this . timeBetweenScansMillis ) ; } catch ( e ) { callbackFn ( null , e ) ; const isChecksumOrFormatError = e instanceof ChecksumException || e instanceof FormatException ; const isNotFound = e instanceof NotFoundException ; if ( isChecksumOrFormatError || isNotFound ) { setTimeout ( loop , this . _timeBetweenDecodingAttempts ) ; } } } ; loop ( ) ; } /**
     * Gets the BinaryBitmap for ya! (and decodes it)
     */ public decode ( element ) { const binaryBitmap = this . createBinaryBitmap ( element ) ; return this . decodeBitmap ( binaryBitmap ) ; } private _isHTMLVideoElement ( mediaElement ) { const potentialVideo = < HTMLVideoElement > mediaElement ; return potentialVideo . videoWidth !== 0 ; } param mediaElement public createBinaryBitmap ( mediaElement ) { const ctx = this . getCaptureCanvasContext ( mediaElement ) ; if ( this . _isHTMLVideoElement ( mediaElement ) ) { this . drawFrameOnCanvas ( < HTMLVideoElement > mediaElement ) ; } else { this . drawImageOnCanvas ( < HTMLImageElement > mediaElement ) ; } const canvas = this . getCaptureCanvas ( mediaElement ) ; const luminanceSource = new HTMLCanvasElementLuminanceSource ( canvas ) ; const hybridBinarizer = new HybridBinarizer ( luminanceSource ) ; return new BinaryBitmap ( hybridBinarizer ) ; } /**
     *
     */ protected getCaptureCanvasContext ( mediaElement ? ) { if ( ! this . captureCanvasContext ) { const elem = this . getCaptureCanvas ( mediaElement ) ; const ctx = elem . getContext ( '2d' ) ; this . captureCanvasContext = ctx ; } return this . captureCanvasContext ; } /**
     *
     */ protected getCaptureCanvas ( mediaElement ? ) { if ( ! this . captureCanvas ) { const elem = this . createCaptureCanvas ( mediaElement ) ; this . captureCanvas = elem ; } return this . captureCanvas ; } /**
      * Overwriting this allows you to manipulate the next frame in anyway you want before decode.
      */ public drawFrameOnCanvas ( srcElement , dimensions = { sx : 0 , sy : 0 , sWidth : srcElement . videoWidth , sHeight : srcElement . videoHeight , dx : 0 , dy : 0 , dWidth : srcElement . videoWidth , dHeight : srcElement . videoHeight } , canvasElementContext = this . captureCanvasContext ) { canvasElementContext . drawImage ( srcElement , dimensions . sx , dimensions . sy , dimensions . sWidth , dimensions . sHeight , dimensions . dx , dimensions . dy , dimensions . dWidth , dimensions . dHeight ) ; } /**
     * Ovewriting this allows you to manipulate the snapshot image in anyway you want before decode.
     */ public drawImageOnCanvas ( srcElement , dimensions = { sx : 0 , sy : 0 , sWidth : srcElement . naturalWidth , sHeight : srcElement . naturalHeight , dx : 0 , dy : 0 , dWidth : srcElement . naturalWidth , dHeight : srcElement . naturalHeight } , canvasElementContext = this . captureCanvasContext ) { canvasElementContext . drawImage ( srcElement , dimensions . sx , dimensions . sy , dimensions . sWidth , dimensions . sHeight , dimensions . dx , dimensions . dy , dimensions . dWidth , dimensions . dHeight ) ; } /**
     * Call the encapsulated readers decode
     */ public decodeBitmap ( binaryBitmap ) { return this . reader . decode ( binaryBitmap , this . _hints ) ; } /**
     * 🖌 Prepares the canvas for capture and scan frames.
     */ public createCaptureCanvas ( mediaElement ? ) { if ( typeof document === 'undefined' ) { this . _destroyCaptureCanvas ( ) ; return null ; } const canvasElement = document . createElement ( 'canvas' ) ; let width ; let height ; if ( typeof mediaElement !== 'undefined' ) { if ( mediaElement instanceof HTMLVideoElement ) { width = mediaElement . videoWidth ; height = mediaElement . videoHeight ; } else if ( mediaElement instanceof HTMLImageElement ) { width = mediaElement . naturalWidth || mediaElement . width ; height = mediaElement . naturalHeight || mediaElement . height ; } } canvasElement . style . width = width + 'px' ; canvasElement . style . height = height + 'px' ; canvasElement . width = width ; canvasElement . height = height ; return canvasElement ; } /**
     * Stops the continuous scan and cleans the stream.
     */ protected stopStreams ( ) { if ( this . stream ) { this . stream . getVideoTracks ( ) . forEach ( t => t . stop ( ) ) ; this . stream = undefined ; } if ( this . _stopAsyncDecode === false ) { this . stopAsyncDecode ( ) ; } if ( this . _stopContinuousDecode === false ) { this . stopContinuousDecode ( ) ; } } memberOf public reset ( ) { this . stopStreams ( ) ; this . _destroyVideoElement ( ) ; this . _destroyImageElement ( ) ; this . _destroyCaptureCanvas ( ) ; } private _destroyVideoElement ( ) { if ( ! this . videoElement ) { return ; } if ( typeof this . videoEndedListener !== 'undefined' ) { this . videoElement . removeEventListener ( 'ended' , this . videoEndedListener ) ; } if ( typeof this . videoPlayingEventListener !== 'undefined' ) { this . videoElement . removeEventListener ( 'playing' , this . videoPlayingEventListener ) ; } if ( typeof this . videoCanPlayListener !== 'undefined' ) { this . videoElement . removeEventListener ( 'loadedmetadata' , this . videoCanPlayListener ) ; } this . cleanVideoSource ( this . videoElement ) ; this . videoElement = undefined ; } private _destroyImageElement ( ) { if ( ! this . imageElement ) { return ; } if ( undefined !== this . imageLoadedListener ) { this . imageElement . removeEventListener ( 'load' , this . imageLoadedListener ) ; } this . imageElement . src = undefined ; this . imageElement . removeAttribute ( 'src' ) ; this . imageElement = undefined ; } /**
     * Cleans canvas references 🖌
     */ private _destroyCaptureCanvas ( ) { this . captureCanvasContext = undefined ; this . captureCanvas = undefined ; } param videoElement param stream public addVideoSource ( videoElement , stream ) { try { videoElement . srcObject = stream ; } catch ( err ) { videoElement . src = URL . createObjectURL ( stream ) ; } } param videoElement private cleanVideoSource ( videoElement ) { try { videoElement . srcObject = null ; } catch ( err ) { videoElement . src = '' ; } this . videoElement . removeAttribute ( 'src' ) ; } }