CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-zxing--library

TypeScript port of ZXing multi-format 1D/2D barcode image processing library

Overview
Eval results
Files

browser-api.mddocs/reference/

Browser APIs

Deprecation Notice

IMPORTANT: The browser APIs documented here are deprecated and moving to the separate @zxing/browser package. These APIs are still available in @zxing/library for backwards compatibility, but new projects should use @zxing/browser instead.

For new projects, install and use:

npm install @zxing/browser

Legacy import (deprecated):

import { BrowserQRCodeReader } from '@zxing/library';

New recommended import:

import { BrowserQRCodeReader } from '@zxing/browser';

Browser-specific implementations for barcode scanning from cameras, video elements, and images in web applications. These classes provide convenient wrappers around the core ZXing readers with built-in support for MediaStream, HTMLVideoElement, and HTMLImageElement.

Package Information

  • Package Name: @zxing/library (deprecated) / @zxing/browser (recommended)
  • Package Type: npm
  • Language: TypeScript
  • Status: Deprecated in @zxing/library, moving to @zxing/browser
  • Browser Support: Modern browsers with MediaDevices API

Core Imports

// Legacy (deprecated)
import {
  BrowserCodeReader,
  BrowserMultiFormatReader,
  BrowserQRCodeReader,
  BrowserBarcodeReader,
  BrowserAztecCodeReader,
  BrowserDatamatrixCodeReader,
  BrowserPDF417Reader,
  BrowserQRCodeSvgWriter,
  BrowserSvgCodeWriter,
  HTMLCanvasElementLuminanceSource,
  VideoInputDevice,
  DecodeContinuouslyCallback
} from '@zxing/library';

Architecture

The browser module provides:

  • Reader Classes: Browser-specific wrappers for each barcode format reader
  • Base Class: BrowserCodeReader provides common camera and video handling
  • SVG Writers: Render barcodes as SVG elements for web display
  • Luminance Source: Convert HTML canvas to luminance data for decoding
  • Device Management: Enumerate and select video input devices
  • Continuous Scanning: Real-time barcode detection with callbacks

Capabilities

BrowserCodeReader

Base class for all browser-based barcode readers with camera support and video/image decoding.

/**
 * Base class for browser code reader with camera support
 * @deprecated Moving to @zxing/browser
 */
class BrowserCodeReader {
  /**
   * Create browser code reader
   * @param reader - Reader: core ZXing reader implementation
   * @param timeBetweenScansMillis - number: milliseconds between scan attempts (default: 500)
   * @param hints - Map<DecodeHintType, any>: optional decode hints
   */
  constructor(
    reader: Reader,
    timeBetweenScansMillis?: number,
    hints?: Map<DecodeHintType, any>
  );

  // Device enumeration
  
  /**
   * List available video input devices
   * @returns Promise<MediaDeviceInfo[]>: array of camera devices
   * @throws Error if device enumeration fails or not supported
   */
  listVideoInputDevices(): Promise<MediaDeviceInfo[]>;
  
  /**
   * Get video input devices (deprecated, use listVideoInputDevices)
   * @returns Promise<VideoInputDevice[]>: array of video devices
   * @deprecated Use listVideoInputDevices() instead
   */
  getVideoInputDevices(): Promise<VideoInputDevice[]>;
  
  /**
   * Find device by ID
   * @param deviceId - string: device identifier
   * @returns Promise<MediaDeviceInfo>: matching device
   * @throws Error if device not found
   */
  findDeviceById(deviceId: string): Promise<MediaDeviceInfo>;

  // Single decode from camera
  
  /**
   * Decode once from video device and return first result
   * @param deviceId - string: optional device ID (undefined for default camera)
   * @param videoSource - string|HTMLVideoElement: optional video element or selector
   * @returns Promise<Result>: first decoded result
   * @throws NotFoundException if no barcode found
   * @throws Error for camera access issues
   */
  decodeOnceFromVideoDevice(
    deviceId?: string,
    videoSource?: string | HTMLVideoElement
  ): Promise<Result>;

  // Continuous decode from camera
  
  /**
   * Continuously decode from video device with callback
   * Calls callback for each frame until stopped
   * @param deviceId - string|null: device ID or null for default camera
   * @param videoSource - string|HTMLVideoElement|null: video element, selector, or null
   * @param callbackFn - DecodeContinuouslyCallback: called with each decode attempt
   * @returns Promise<void>: resolves when streaming starts, rejects on error
   * @throws Error for camera access issues
   */
  decodeFromVideoDevice(
    deviceId: string | null,
    videoSource: string | HTMLVideoElement | null,
    callbackFn: DecodeContinuouslyCallback
  ): Promise<void>;

  // Decode from constraints
  
  /**
   * Decode once from media stream constraints
   * @param constraints - MediaStreamConstraints: camera/audio constraints
   * @param videoSource - string|HTMLVideoElement: optional video element
   * @returns Promise<Result>: decoded result
   * @throws NotFoundException if no barcode found
   * @throws Error for camera access issues
   */
  decodeOnceFromConstraints(
    constraints: MediaStreamConstraints,
    videoSource?: string | HTMLVideoElement
  ): Promise<Result>;

  /**
   * Continuously decode from media stream constraints
   * @param constraints - MediaStreamConstraints: camera/audio constraints
   * @param videoSource - string|HTMLVideoElement: video element or selector
   * @param callbackFn - DecodeContinuouslyCallback: decode callback
   * @returns Promise<void>: resolves when streaming starts
   * @throws Error for camera access issues
   */
  decodeFromConstraints(
    constraints: MediaStreamConstraints,
    videoSource: string | HTMLVideoElement,
    callbackFn: DecodeContinuouslyCallback
  ): Promise<void>;

  // Decode from stream
  
  /**
   * Decode once from existing media stream
   * @param stream - MediaStream: active media stream
   * @param videoSource - string|HTMLVideoElement: optional video element
   * @returns Promise<Result>: decoded result
   * @throws NotFoundException if no barcode found
   */
  decodeOnceFromStream(
    stream: MediaStream,
    videoSource?: string | HTMLVideoElement
  ): Promise<Result>;

  /**
   * Continuously decode from existing media stream
   * @param stream - MediaStream: active media stream
   * @param videoSource - string|HTMLVideoElement: video element or selector
   * @param callbackFn - DecodeContinuouslyCallback: decode callback
   * @returns Promise<void>: resolves when streaming starts
   */
  decodeFromStream(
    stream: MediaStream,
    videoSource: string | HTMLVideoElement,
    callbackFn: DecodeContinuouslyCallback
  ): Promise<void>;

  // Decode from image
  
  /**
   * Decode from image source or URL
   * @param source - string|HTMLImageElement: optional image element, selector, or URL
   * @param url - string: optional image URL if source not provided
   * @returns Promise<Result>: decoded result
   * @throws NotFoundException if no barcode found
   */
  decodeFromImage(
    source?: string | HTMLImageElement,
    url?: string
  ): Promise<Result>;

  /**
   * Decode from HTML image element
   * @param source - string|HTMLImageElement: image element or selector
   * @returns Promise<Result>: decoded result
   * @throws NotFoundException if no barcode found
   */
  decodeFromImageElement(
    source: string | HTMLImageElement
  ): Promise<Result>;

  /**
   * Decode from image URL
   * @param url - string: optional image URL (prompts file picker if not provided)
   * @returns Promise<Result>: decoded result
   * @throws NotFoundException if no barcode found
   * @throws Error for image loading issues
   */
  decodeFromImageUrl(url?: string): Promise<Result>;

  // Decode from video element
  
  /**
   * Decode once from video element
   * @param source - string|HTMLVideoElement: video element or selector
   * @returns Promise<Result>: decoded result
   * @throws NotFoundException if no barcode found
   */
  decodeFromVideoElement(
    source: string | HTMLVideoElement
  ): Promise<Result>;

  /**
   * Continuously decode from video element
   * @param source - string|HTMLVideoElement: video element or selector
   * @param callbackFn - DecodeContinuouslyCallback: decode callback
   * @returns Promise<void>: resolves when decoding starts
   */
  decodeFromVideoElementContinuously(
    source: string | HTMLVideoElement,
    callbackFn: DecodeContinuouslyCallback
  ): Promise<void>;

  /**
   * Decode from video file URL
   * @param url - string: video file URL
   * @returns Promise<Result>: decoded result
   * @throws NotFoundException if no barcode found
   */
  decodeFromVideoUrl(url: string): Promise<Result>;

  /**
   * Continuously decode from video file URL
   * @param url - string: video file URL
   * @param callbackFn - DecodeContinuouslyCallback: decode callback
   * @returns Promise<void>: resolves when decoding starts
   */
  decodeFromVideoUrlContinuously(
    url: string,
    callbackFn: DecodeContinuouslyCallback
  ): Promise<void>;

  // Control methods
  
  /**
   * Stop asynchronous decoding and release resources
   */
  stopAsyncDecode(): void;
  
  /**
   * Stop continuous decoding
   */
  stopContinuousDecode(): void;
  
  /**
   * Reset reader and release camera
   */
  reset(): void;

  // Properties
  
  /**
   * Get time between decoding attempts in milliseconds
   * @returns number: milliseconds between scans
   */
  get timeBetweenDecodingAttempts(): number;
  
  /**
   * Set time between decoding attempts
   * @param millis - number: milliseconds between scans
   */
  set timeBetweenDecodingAttempts(millis: number);
  
  /**
   * Get decode hints
   * @returns Map<DecodeHintType, any>: current hints
   */
  get hints(): Map<DecodeHintType, any>;
  
  /**
   * Set decode hints
   * @param hints - Map<DecodeHintType, any>: new hints
   */
  set hints(hints: Map<DecodeHintType, any>);

  // Static utility methods
  
  /**
   * Check if MediaDevices API is supported
   * @returns boolean: true if supported
   */
  static isMediaDevicesSupported(): boolean;
  
  /**
   * Check if device enumeration is supported
   * @returns boolean: true if enumerateDevices is available
   */
  static canEnumerateDevices(): boolean;
}

Usage Examples:

Basic camera scanning:

import { BrowserQRCodeReader, Result } from '@zxing/library';

const codeReader = new BrowserQRCodeReader();

// Check browser support
if (!BrowserCodeReader.isMediaDevicesSupported()) {
  console.error('MediaDevices not supported in this browser');
  return;
}

// Decode once from default camera
try {
  const result: Result = await codeReader.decodeOnceFromVideoDevice(undefined, 'video');
  console.log('Decoded:', result.getText());
  console.log('Format:', result.getBarcodeFormat());
} catch (err) {
  console.error('Scanning failed:', err);
} finally {
  // Always clean up resources
  codeReader.reset();
}

Continuous scanning from camera:

import { BrowserQRCodeReader, Result, Exception, NotFoundException } from '@zxing/library';

const codeReader = new BrowserQRCodeReader();
const videoElement = document.getElementById('video') as HTMLVideoElement;

// Continuous decode with callback
const callback = (result: Result | null, error: Exception | undefined) => {
  if (result) {
    console.log('Found:', result.getText());
    console.log('Format:', result.getBarcodeFormat());
    
    // Display result
    document.getElementById('result')!.textContent = result.getText();
    
    // Optionally stop scanning after first result
    // codeReader.stopContinuousDecode();
    // codeReader.reset();
  }
  
  if (error) {
    // NotFoundException is expected when no barcode is in frame
    if (!(error instanceof NotFoundException)) {
      console.error('Decode error:', error);
    }
  }
};

try {
  await codeReader.decodeFromVideoDevice(null, videoElement, callback);
} catch (err) {
  console.error('Failed to start camera:', err);
}

// Stop scanning later
// codeReader.stopContinuousDecode();
// codeReader.reset();

Select specific camera:

import { BrowserQRCodeReader, Result } from '@zxing/library';

const codeReader = new BrowserQRCodeReader();

// List available cameras
const videoInputDevices: MediaDeviceInfo[] = await codeReader.listVideoInputDevices();
console.log('Available cameras:', videoInputDevices);

videoInputDevices.forEach((device, index) => {
  console.log(`Camera ${index}: ${device.label || 'Unknown'}`);
  console.log(`  Device ID: ${device.deviceId}`);
  console.log(`  Group ID: ${device.groupId}`);
});

// Use specific camera (e.g., back camera)
const selectedDeviceId: string = videoInputDevices[0].deviceId;
try {
  const result: Result = await codeReader.decodeOnceFromVideoDevice(
    selectedDeviceId,
    'video'
  );
  console.log('Decoded:', result.getText());
} finally {
  codeReader.reset();
}

Decode from image:

import { BrowserQRCodeReader, Result } from '@zxing/library';

const codeReader = new BrowserQRCodeReader();

// From image URL
try {
  const result: Result = await codeReader.decodeFromImageUrl('/path/to/barcode.png');
  console.log('Decoded:', result.getText());
} catch (error) {
  console.error('Image decode failed:', error);
}

// From image element
const imgElement = document.getElementById('barcode-img') as HTMLImageElement;
try {
  const result2: Result = await codeReader.decodeFromImageElement(imgElement);
  console.log('Decoded:', result2.getText());
} catch (error) {
  console.error('Image decode failed:', error);
}

// From file picker (no URL specified)
try {
  const result3: Result = await codeReader.decodeFromImageUrl();
  console.log('User selected image decoded:', result3.getText());
} catch (error) {
  console.error('User cancelled or decode failed:', error);
}

BrowserMultiFormatReader

Multi-format browser reader that can decode any supported barcode format.

/**
 * Multi-format browser reader for decoding multiple barcode formats
 * @deprecated Moving to @zxing/browser
 */
class BrowserMultiFormatReader extends BrowserCodeReader {
  /**
   * Create multi-format browser reader
   * @param hints - Map<DecodeHintType, any>: optional decode hints for format filtering
   * @param timeBetweenScansMillis - number: milliseconds between scan attempts (default: 500)
   */
  constructor(
    hints?: Map<DecodeHintType, any>,
    timeBetweenScansMillis?: number
  );
}

Usage Examples:

import { BrowserMultiFormatReader, DecodeHintType, BarcodeFormat, Result } from '@zxing/library';

// Create reader with format hints
const hints = new Map<DecodeHintType, any>();
hints.set(DecodeHintType.POSSIBLE_FORMATS, [
  BarcodeFormat.QR_CODE,
  BarcodeFormat.EAN_13,
  BarcodeFormat.CODE_128
]);

const codeReader = new BrowserMultiFormatReader(hints, 500);

// Use like BrowserCodeReader
try {
  const result: Result = await codeReader.decodeOnceFromVideoDevice(undefined, 'video');
  console.log('Format:', result.getBarcodeFormat());
  console.log('Text:', result.getText());
} finally {
  codeReader.reset();
}

Try harder for better accuracy:

import { BrowserMultiFormatReader, DecodeHintType } from '@zxing/library';

const hints = new Map<DecodeHintType, any>();
hints.set(DecodeHintType.TRY_HARDER, true);
hints.set(DecodeHintType.POSSIBLE_FORMATS, [BarcodeFormat.QR_CODE]);

const codeReader = new BrowserMultiFormatReader(hints);

try {
  const result: Result = await codeReader.decodeFromImageUrl('/difficult-barcode.jpg');
  console.log('Decoded:', result.getText());
} catch (error) {
  console.error('Could not decode:', error);
}

BrowserQRCodeReader

QR Code-specific browser reader.

/**
 * QR Code reader for browser
 * @deprecated Moving to @zxing/browser
 */
class BrowserQRCodeReader extends BrowserCodeReader {
  /**
   * Create QR Code browser reader
   * @param timeBetweenScansMillis - number: milliseconds between scan attempts (default: 500)
   */
  constructor(timeBetweenScansMillis?: number);
}

Usage Examples:

import { BrowserQRCodeReader, Result } from '@zxing/library';

const qrReader = new BrowserQRCodeReader();

// Scan QR codes from camera
try {
  const result: Result = await qrReader.decodeOnceFromVideoDevice(undefined, 'video');
  console.log('QR Code:', result.getText());
} catch (error) {
  console.error('QR scanning failed:', error);
} finally {
  qrReader.reset();
}

BrowserBarcodeReader

1D barcode reader for browser (supports all 1D formats like UPC, EAN, Code128, etc.).

/**
 * 1D barcode reader for browser
 * Supports UPC, EAN, Code 128, Code 39, Code 93, ITF, Codabar, RSS
 * @deprecated Moving to @zxing/browser
 */
class BrowserBarcodeReader extends BrowserCodeReader {
  /**
   * Create 1D barcode browser reader
   * @param timeBetweenScansMillis - number: milliseconds between scan attempts (default: 500)
   * @param hints - Map<DecodeHintType, any>: optional decode hints for format filtering
   */
  constructor(
    timeBetweenScansMillis?: number,
    hints?: Map<DecodeHintType, any>
  );
}

Usage Examples:

import { BrowserBarcodeReader, DecodeHintType, BarcodeFormat, Result } from '@zxing/library';

// Configure for specific 1D formats
const hints = new Map<DecodeHintType, any>();
hints.set(DecodeHintType.POSSIBLE_FORMATS, [
  BarcodeFormat.EAN_13,
  BarcodeFormat.CODE_128,
  BarcodeFormat.UPC_A
]);

const barcodeReader = new BrowserBarcodeReader(500, hints);

// Scan product barcodes
try {
  const result: Result = await barcodeReader.decodeOnceFromVideoDevice(undefined, 'video');
  console.log('Barcode:', result.getText());
  console.log('Format:', result.getBarcodeFormat());
} catch (error) {
  console.error('Barcode scanning failed:', error);
} finally {
  barcodeReader.reset();
}

BrowserAztecCodeReader

Aztec Code reader for browser.

/**
 * Aztec Code reader for browser
 * @deprecated Moving to @zxing/browser
 */
class BrowserAztecCodeReader extends BrowserCodeReader {
  /**
   * Create Aztec Code browser reader
   * @param timeBetweenScansMillis - number: milliseconds between scan attempts (default: 500)
   */
  constructor(timeBetweenScansMillis?: number);
}

Usage Examples:

import { BrowserAztecCodeReader, Result } from '@zxing/library';

const aztecReader = new BrowserAztecCodeReader();
try {
  const result: Result = await aztecReader.decodeFromImageUrl('/aztec-code.png');
  console.log('Aztec Code:', result.getText());
} catch (error) {
  console.error('Aztec decoding failed:', error);
}

BrowserDatamatrixCodeReader

Data Matrix reader for browser.

/**
 * Data Matrix reader for browser
 * @deprecated Moving to @zxing/browser
 */
class BrowserDatamatrixCodeReader extends BrowserCodeReader {
  /**
   * Create Data Matrix browser reader
   * @param timeBetweenScansMillis - number: milliseconds between scan attempts (default: 500)
   */
  constructor(timeBetweenScansMillis?: number);
}

Usage Examples:

import { BrowserDatamatrixCodeReader, Result } from '@zxing/library';

const dmReader = new BrowserDatamatrixCodeReader();
try {
  const result: Result = await dmReader.decodeOnceFromVideoDevice(undefined, 'video');
  console.log('Data Matrix:', result.getText());
} catch (error) {
  console.error('Data Matrix scanning failed:', error);
} finally {
  dmReader.reset();
}

BrowserPDF417Reader

PDF417 reader for browser.

/**
 * PDF417 reader for browser
 * @deprecated Moving to @zxing/browser
 */
class BrowserPDF417Reader extends BrowserCodeReader {
  /**
   * Create PDF417 browser reader
   * @param timeBetweenScansMillis - number: milliseconds between scan attempts (default: 500)
   */
  constructor(timeBetweenScansMillis?: number);
}

Usage Examples:

import { BrowserPDF417Reader, Result } from '@zxing/library';

const pdf417Reader = new BrowserPDF417Reader();
try {
  const result: Result = await pdf417Reader.decodeFromImageUrl('/document.png');
  console.log('PDF417 data:', result.getText());
  
  // Access PDF417-specific metadata
  const metadata = result.getResultMetadata().get(ResultMetadataType.PDF417_EXTRA_METADATA);
  if (metadata) {
    console.log('Segment index:', metadata.getSegmentIndex());
  }
} catch (error) {
  console.error('PDF417 decoding failed:', error);
}

BrowserQRCodeSvgWriter

QR Code SVG writer for rendering QR codes as SVG elements.

/**
 * QR Code SVG writer for browser
 * @deprecated Moving to @zxing/browser
 */
class BrowserQRCodeSvgWriter {
  /**
   * Write and render a QR Code as SVG element
   * @param contents - string: text to encode
   * @param width - number: width in pixels
   * @param height - number: height in pixels
   * @param hints - Map<EncodeHintType, any>: optional encoding hints (error correction, margin, etc.)
   * @returns SVGSVGElement: generated SVG element
   * @throws WriterException if encoding fails
   */
  write(
    contents: string,
    width: number,
    height: number,
    hints?: Map<EncodeHintType, any>
  ): SVGSVGElement;

  /**
   * Render QR Code and append to DOM element
   * @param containerElement - string|HTMLElement: container element or selector
   * @param contents - string: text to encode
   * @param width - number: width in pixels
   * @param height - number: height in pixels
   * @param hints - Map<EncodeHintType, any>: optional encoding hints
   * @throws WriterException if encoding fails
   * @throws Error if container not found
   */
  writeToDom(
    containerElement: string | HTMLElement,
    contents: string,
    width: number,
    height: number,
    hints?: Map<EncodeHintType, any>
  ): void;
}

Usage Examples:

Generate QR Code SVG:

import { BrowserQRCodeSvgWriter, EncodeHintType } from '@zxing/library';

const svgWriter = new BrowserQRCodeSvgWriter();

// Create SVG element
const svgElement: SVGSVGElement = svgWriter.write('https://example.com', 300, 300);
document.body.appendChild(svgElement);

// Style SVG element
svgElement.style.border = '1px solid black';
svgElement.style.margin = '10px';

With encoding hints:

import {
  BrowserQRCodeSvgWriter,
  EncodeHintType,
  QRCodeDecoderErrorCorrectionLevel
} from '@zxing/library';

const hints = new Map<EncodeHintType, any>();
hints.set(EncodeHintType.ERROR_CORRECTION, QRCodeDecoderErrorCorrectionLevel.H);
hints.set(EncodeHintType.MARGIN, 2);
hints.set(EncodeHintType.CHARACTER_SET, 'UTF-8');

const svgWriter = new BrowserQRCodeSvgWriter();
const svgElement: SVGSVGElement = svgWriter.write('Important data', 300, 300, hints);

// Or write directly to DOM
svgWriter.writeToDom('#qr-container', 'Important data', 300, 300, hints);

BrowserSvgCodeWriter

Generic SVG barcode writer (abstract base class).

/**
 * Generic SVG barcode writer abstract base class
 * @deprecated Moving to @zxing/browser
 */
abstract class BrowserSvgCodeWriter {
  /**
   * Create SVG writer with container
   * @param containerElement - string|HTMLElement: container element or selector
   */
  constructor(containerElement: string | HTMLElement);

  /**
   * Write barcode to SVG
   * @param contents - string: text to encode
   * @param width - number: width in pixels
   * @param height - number: height in pixels
   * @param hints - Map<EncodeHintType, any>: optional encoding hints
   * @returns SVGSVGElement: generated SVG element
   * @throws WriterException if encoding fails
   */
  write(
    contents: string,
    width: number,
    height: number,
    hints?: Map<EncodeHintType, any>
  ): SVGSVGElement;
}

HTMLCanvasElementLuminanceSource

Luminance source for converting HTML canvas elements to luminance data for barcode decoding.

/**
 * Luminance source from HTML canvas element
 * Extracts grayscale pixel data from canvas for barcode decoding
 * @deprecated Moving to @zxing/browser
 */
class HTMLCanvasElementLuminanceSource extends LuminanceSource {
  /**
   * Create luminance source from canvas
   * @param canvas - HTMLCanvasElement: canvas element with barcode image
   * @param doAutoInvert - boolean: optional auto-invert for white-on-black (default: false)
   */
  constructor(canvas: HTMLCanvasElement, doAutoInvert?: boolean);

  // Inherited from LuminanceSource
  
  /**
   * Get single row of luminance data
   * @param y - number: row index in [0, height)
   * @param row - Uint8ClampedArray: optional pre-allocated array
   * @returns Uint8ClampedArray: grayscale values (0-255)
   */
  getRow(y: number, row: Uint8ClampedArray): Uint8ClampedArray;
  
  /**
   * Get entire luminance matrix
   * @returns Uint8ClampedArray: all luminance values in row-major order
   */
  getMatrix(): Uint8ClampedArray;
  
  /**
   * Check if cropping is supported
   * @returns boolean: true (cropping is supported)
   */
  isCropSupported(): boolean;
  
  /**
   * Create cropped luminance source
   * @param left - number: left coordinate
   * @param top - number: top coordinate
   * @param width - number: crop width
   * @param height - number: crop height
   * @returns LuminanceSource: cropped source
   */
  crop(left: number, top: number, width: number, height: number): LuminanceSource;
  
  /**
   * Check if rotation is supported
   * @returns boolean: true (rotation is supported)
   */
  isRotateSupported(): boolean;
  
  /**
   * Create 90° counterclockwise rotated source
   * @returns LuminanceSource: rotated source
   */
  rotateCounterClockwise(): LuminanceSource;
  
  /**
   * Create 45° counterclockwise rotated source
   * @returns LuminanceSource: rotated source
   */
  rotateCounterClockwise45(): LuminanceSource;
  
  /**
   * Create inverted luminance source (black↔white)
   * @returns LuminanceSource: inverted source
   */
  invert(): LuminanceSource;
}

Usage Examples:

import {
  HTMLCanvasElementLuminanceSource,
  BinaryBitmap,
  HybridBinarizer,
  QRCodeReader,
  Result
} from '@zxing/library';

// Create canvas from image
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
const img = document.getElementById('barcode-img') as HTMLImageElement;
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);

// Create luminance source
const luminanceSource = new HTMLCanvasElementLuminanceSource(canvas);
const binarizer = new HybridBinarizer(luminanceSource);
const binaryBitmap = new BinaryBitmap(binarizer);

// Decode
const reader = new QRCodeReader();
try {
  const result: Result = reader.decode(binaryBitmap);
  console.log('Decoded:', result.getText());
} catch (error) {
  console.error('Decode failed:', error);
}

// With auto-invert for white-on-black barcodes
const luminanceSourceInvert = new HTMLCanvasElementLuminanceSource(canvas, true);
const binarizerInvert = new HybridBinarizer(luminanceSourceInvert);
const bitmapInvert = new BinaryBitmap(binarizerInvert);

try {
  const result: Result = reader.decode(bitmapInvert);
  console.log('Decoded (inverted):', result.getText());
} catch (error) {
  console.error('Decode failed:', error);
}

VideoInputDevice

Video input device metadata containing device ID and label.

/**
 * Video input device metadata
 * @deprecated Moving to @zxing/browser. Use MediaDeviceInfo directly instead.
 */
class VideoInputDevice implements MediaDeviceInfo {
  /**
   * Create video input device
   * @param deviceId - string: unique device identifier
   * @param label - string: human-readable device name
   * @param groupId - string: optional group identifier
   */
  constructor(deviceId: string, label: string, groupId?: string);

  /**
   * Unique device identifier
   */
  readonly deviceId: string;
  
  /**
   * Human-readable device label
   */
  readonly label: string;
  
  /**
   * Device group identifier
   */
  readonly groupId: string;
  
  /**
   * Device kind (always "videoinput")
   */
  readonly kind: 'videoinput';

  /**
   * Convert to JSON object
   * @returns Object: device information as plain object
   */
  toJSON(): {
    kind: string;
    groupId: string;
    deviceId: string;
    label: string;
  };
}

Usage Examples:

import { BrowserQRCodeReader, VideoInputDevice } from '@zxing/library';

const codeReader = new BrowserQRCodeReader();

// Get devices (deprecated method)
const devices: VideoInputDevice[] = await codeReader.getVideoInputDevices();

devices.forEach(device => {
  console.log('Device ID:', device.deviceId);
  console.log('Label:', device.label);
  console.log('Group ID:', device.groupId);
  console.log('Kind:', device.kind); // "videoinput"
  
  // Convert to JSON
  const json = device.toJSON();
  console.log('JSON:', json);
});

// Use modern method instead (returns MediaDeviceInfo directly)
const mediaDevices: MediaDeviceInfo[] = await codeReader.listVideoInputDevices();

DecodeContinuouslyCallback

Callback function type for continuous barcode decoding.

/**
 * Callback format for continuous decode scanning
 * Called for each frame during continuous scanning
 * @param result - Result|null: decode result if successful, null if error occurred
 * @param error - Exception: optional exception if decode failed
 */
type DecodeContinuouslyCallback = (result: Result | null, error?: Exception) => any;

Usage Examples:

import {
  BrowserMultiFormatReader,
  Result,
  Exception,
  NotFoundException,
  DecodeContinuouslyCallback
} from '@zxing/library';

const codeReader = new BrowserMultiFormatReader();
const videoElement = document.getElementById('video') as HTMLVideoElement;

const callback: DecodeContinuouslyCallback = (result, error) => {
  if (result) {
    console.log('Decoded:', result.getText());
    console.log('Format:', result.getBarcodeFormat());

    // Display result
    document.getElementById('result')!.textContent = result.getText();
    
    // Access result points (barcode location)
    const points = result.getResultPoints();
    console.log(`Found at ${points.length} points`);
  }

  if (error) {
    // NotFoundException is expected when no barcode is in frame
    if (!(error instanceof NotFoundException)) {
      console.error('Decode error:', error);
    }
  }
};

// Start continuous scanning
try {
  await codeReader.decodeFromVideoDevice(null, videoElement, callback);
} catch (err) {
  console.error('Failed to start scanning:', err);
}

// Stop scanning when done
// codeReader.stopContinuousDecode();
// codeReader.reset();

Advanced continuous scanning with controls:

import { BrowserQRCodeReader, Result, Exception, NotFoundException } from '@zxing/library';

const codeReader = new BrowserQRCodeReader();
let lastResult: string | null = null;
let scanning = true;

const callback: DecodeContinuouslyCallback = (result, error) => {
  if (!scanning) return;

  if (result) {
    const text: string = result.getText();

    // Avoid processing duplicate scans
    if (text !== lastResult) {
      lastResult = text;
      console.log('New code detected:', text);

      // Play beep sound
      const beepAudio = document.getElementById('beep-audio') as HTMLAudioElement;
      beepAudio.play().catch(e => console.warn('Audio play failed:', e));

      // Show result
      document.getElementById('result')!.textContent = text;
      
      // Optionally stop after first unique scan
      // scanning = false;
      // codeReader.stopContinuousDecode();
    }
  }
  
  if (error && !(error instanceof NotFoundException)) {
    console.error('Unexpected error:', error);
  }
};

// Start button
document.getElementById('start-btn')!.addEventListener('click', async () => {
  scanning = true;
  lastResult = null;
  const videoElement = document.getElementById('video') as HTMLVideoElement;
  
  try {
    await codeReader.decodeFromVideoDevice(null, videoElement, callback);
  } catch (err) {
    console.error('Failed to start camera:', err);
    alert('Camera access denied or not available');
  }
});

// Stop button
document.getElementById('stop-btn')!.addEventListener('click', () => {
  scanning = false;
  codeReader.stopContinuousDecode();
  codeReader.reset();
  lastResult = null;
});

HTMLVisualMediaElement

Type definition for HTML elements that can be used as barcode decode sources.

/**
 * Union type for HTML media elements that can be decoded
 * Includes video elements (from camera/video files) and image elements
 */
type HTMLVisualMediaElement = HTMLVideoElement | HTMLImageElement;

Usage Examples:

import { BrowserQRCodeReader, HTMLVisualMediaElement, Result } from '@zxing/library';

// Type-safe media element handling
async function decodeFromMediaElement(
  element: HTMLVisualMediaElement,
  reader: BrowserQRCodeReader
): Promise<Result> {
  if (element instanceof HTMLVideoElement) {
    console.log('Decoding from video element');
    return reader.decodeFromVideoElement(element);
  } else {
    console.log('Decoding from image element');
    return reader.decodeFromImageElement(element);
  }
}

// Use with video element
const videoElement = document.getElementById('video') as HTMLVideoElement;
const reader = new BrowserQRCodeReader();
const result1 = await decodeFromMediaElement(videoElement, reader);

// Use with image element
const imageElement = document.getElementById('barcode-img') as HTMLImageElement;
const result2 = await decodeFromMediaElement(imageElement, reader);

// Generic decoder wrapper class
class MediaDecoder {
  constructor(private reader: BrowserQRCodeReader) {}

  async decode(media: HTMLVisualMediaElement): Promise<string | null> {
    try {
      const result: Result = await decodeFromMediaElement(media, this.reader);
      return result.getText();
    } catch (e) {
      console.error('Decode failed:', e);
      return null;
    }
  }

  isVideoElement(media: HTMLVisualMediaElement): media is HTMLVideoElement {
    return media instanceof HTMLVideoElement;
  }

  isImageElement(media: HTMLVisualMediaElement): media is HTMLImageElement {
    return media instanceof HTMLImageElement;
  }
}

// Type guards for media elements
function handleMediaSource(source: string | HTMLVisualMediaElement): HTMLVisualMediaElement {
  if (typeof source === 'string') {
    // Source is a selector or URL
    const element = document.querySelector(source) as HTMLVisualMediaElement;
    if (!element) {
      throw new Error(`Element not found: ${source}`);
    }
    return element;
  } else {
    // Source is already an element
    return source;
  }
}

// Multi-source decoder
async function decodeFromAnySources(
  sources: HTMLVisualMediaElement[],
  reader: BrowserQRCodeReader
): Promise<Result[]> {
  const results: Result[] = [];

  for (const source of sources) {
    try {
      const result: Result = await decodeFromMediaElement(source, reader);
      results.push(result);
    } catch (e) {
      console.warn(`Failed to decode from ${source.tagName}:`, e);
    }
  }

  return results;
}

// Usage with multiple sources
const allElements: HTMLVisualMediaElement[] = [
  document.getElementById('video1') as HTMLVideoElement,
  document.getElementById('img1') as HTMLImageElement,
  document.getElementById('video2') as HTMLVideoElement,
  document.getElementById('img2') as HTMLImageElement
];

const decoder = new MediaDecoder(new BrowserQRCodeReader());
const results: Result[] = await decodeFromAnySources(allElements, new BrowserQRCodeReader());
console.log(`Decoded ${results.length} barcodes`);

Complete Camera Scanner Example

Full example with camera selection, continuous scanning, and cleanup:

import {
  BrowserMultiFormatReader,
  Result,
  Exception,
  NotFoundException,
  DecodeHintType,
  BarcodeFormat
} from '@zxing/library';

class BarcodeScanner {
  private codeReader: BrowserMultiFormatReader;
  private selectedDeviceId: string | null = null;

  constructor() {
    // Configure reader with hints
    const hints = new Map<DecodeHintType, any>();
    hints.set(DecodeHintType.POSSIBLE_FORMATS, [
      BarcodeFormat.QR_CODE,
      BarcodeFormat.EAN_13,
      BarcodeFormat.CODE_128
    ]);

    this.codeReader = new BrowserMultiFormatReader(hints, 300);
  }

  async initializeCameraList(): Promise<void> {
    const videoSelect = document.getElementById('camera-select') as HTMLSelectElement;

    try {
      const devices: MediaDeviceInfo[] = await this.codeReader.listVideoInputDevices();

      // Clear existing options
      videoSelect.innerHTML = '';

      // Add camera options
      devices.forEach((device, index) => {
        const option = document.createElement('option');
        option.value = device.deviceId;
        option.text = device.label || `Camera ${index + 1}`;
        videoSelect.appendChild(option);
      });

      // Select first camera by default
      if (devices.length > 0) {
        this.selectedDeviceId = devices[0].deviceId;
      } else {
        throw new Error('No cameras found');
      }
    } catch (err) {
      console.error('Failed to enumerate devices:', err);
      throw err;
    }
  }

  async startScanning(): Promise<void> {
    const videoElement = document.getElementById('video') as HTMLVideoElement;
    const resultElement = document.getElementById('result')!;

    const callback: DecodeContinuouslyCallback = (result, error) => {
      if (result) {
        resultElement.textContent = `${result.getBarcodeFormat()}: ${result.getText()}`;

        // Optional: stop after first successful scan
        // this.stopScanning();
      }

      if (error && !(error instanceof NotFoundException)) {
        console.error('Decode error:', error);
      }
    };

    try {
      await this.codeReader.decodeFromVideoDevice(
        this.selectedDeviceId,
        videoElement,
        callback
      );
    } catch (err) {
      console.error('Failed to start scanning:', err);
      throw err;
    }
  }

  stopScanning(): void {
    this.codeReader.stopContinuousDecode();
    this.codeReader.reset();
  }

  async scanImage(imageUrl: string): Promise<string | null> {
    try {
      const result: Result = await this.codeReader.decodeFromImageUrl(imageUrl);
      return result.getText();
    } catch (err) {
      console.error('Failed to decode image:', err);
      return null;
    }
  }
  
  setDevice(deviceId: string): void {
    this.selectedDeviceId = deviceId;
  }
}

// Usage
const scanner = new BarcodeScanner();

// Initialize camera list
await scanner.initializeCameraList();

// Start scanning button
document.getElementById('start-btn')!.addEventListener('click', async () => {
  try {
    await scanner.startScanning();
  } catch (err) {
    alert('Failed to start camera: ' + err.message);
  }
});

// Stop scanning button
document.getElementById('stop-btn')!.addEventListener('click', () => {
  scanner.stopScanning();
});

// Camera selection
document.getElementById('camera-select')!.addEventListener('change', (e) => {
  const select = e.target as HTMLSelectElement;
  scanner.setDevice(select.value);
  
  // Restart scanning with new device
  scanner.stopScanning();
  scanner.startScanning().catch(err => {
    console.error('Failed to switch camera:', err);
  });
});

// Clean up on page unload
window.addEventListener('beforeunload', () => {
  scanner.stopScanning();
});

Best Practices

Camera Permissions

Always handle camera permission errors gracefully:

import { BrowserQRCodeReader } from '@zxing/library';

const codeReader = new BrowserQRCodeReader();

try {
  await codeReader.decodeOnceFromVideoDevice(undefined, 'video');
} catch (err: any) {
  // Handle specific camera errors
  if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
    alert('Camera permission denied. Please allow camera access.');
  } else if (err.name === 'NotFoundError' || err.name === 'DevicesNotFoundError') {
    alert('No camera found on this device.');
  } else if (err.name === 'NotReadableError' || err.name === 'TrackStartError') {
    alert('Camera is already in use by another application.');
  } else if (err.name === 'OverconstrainedError' || err.name === 'ConstraintNotSatisfiedError') {
    alert('Camera does not meet the required constraints.');
  } else if (err.name === 'SecurityError') {
    alert('Camera access is blocked due to security policy (HTTPS required).');
  } else {
    console.error('Camera error:', err);
    alert('Failed to access camera: ' + err.message);
  }
} finally {
  codeReader.reset();
}

Resource Cleanup

Always clean up resources when done:

import { BrowserQRCodeReader } from '@zxing/library';

const codeReader = new BrowserQRCodeReader();

// Pattern 1: try/finally
try {
  await codeReader.decodeOnceFromVideoDevice(undefined, 'video');
} finally {
  // Always release camera
  codeReader.reset();
}

// Pattern 2: explicit cleanup
const result = await codeReader.decodeOnceFromVideoDevice(undefined, 'video');
console.log('Result:', result.getText());
codeReader.stopContinuousDecode(); // Stop any async operations
codeReader.reset(); // Release camera

// Pattern 3: cleanup handler
function withCleanup<T>(
  fn: (reader: BrowserQRCodeReader) => Promise<T>
): Promise<T> {
  const reader = new BrowserQRCodeReader();
  
  return fn(reader).finally(() => {
    reader.reset();
  });
}

// Usage
const text = await withCleanup(async (reader) => {
  const result = await reader.decodeOnceFromVideoDevice(undefined, 'video');
  return result.getText();
});

Performance Tuning

Adjust scan frequency for performance vs responsiveness trade-off:

import { BrowserQRCodeReader } from '@zxing/library';

// Faster scanning (more CPU intensive, more responsive)
const fastReader = new BrowserQRCodeReader(100); // 100ms between scans = 10 fps

// Slower scanning (less CPU intensive, less responsive)
const slowReader = new BrowserQRCodeReader(1000); // 1 second between scans = 1 fps

// Balanced (recommended default)
const balancedReader = new BrowserQRCodeReader(500); // 500ms = 2 fps

// Dynamic adjustment
const adaptiveReader = new BrowserQRCodeReader(500);

// Increase frequency when barcode detected nearby
function onBarcodeNear() {
  adaptiveReader.timeBetweenDecodingAttempts = 100; // Scan faster
}

// Decrease frequency when no barcode
function onBarcodeAway() {
  adaptiveReader.timeBetweenDecodingAttempts = 1000; // Scan slower
}

Error Handling in Continuous Mode

Handle different error types appropriately:

import {
  BrowserMultiFormatReader,
  Result,
  Exception,
  NotFoundException,
  ChecksumException,
  FormatException,
  DecodeContinuouslyCallback
} from '@zxing/library';

const codeReader = new BrowserMultiFormatReader();
const videoElement = document.getElementById('video') as HTMLVideoElement;

const callback: DecodeContinuouslyCallback = (result, error) => {
  if (result) {
    console.log('Success:', result.getText());
    
    // Optionally stop scanning on success
    // codeReader.stopContinuousDecode();
    // codeReader.reset();
  }

  if (error) {
    if (error instanceof NotFoundException) {
      // Normal - no barcode in frame, keep scanning silently
      // Don't log this to avoid console spam
    } else if (error instanceof ChecksumException) {
      // Barcode detected but checksum failed, keep scanning
      console.debug('Checksum failed, retrying...');
    } else if (error instanceof FormatException) {
      // Invalid barcode format, keep scanning
      console.debug('Invalid format, retrying...');
    } else {
      // Unexpected error - may want to stop
      console.error('Unexpected error:', error);
      codeReader.stopContinuousDecode();
      codeReader.reset();
      alert('Scanner error: ' + error.message);
    }
  }
};

try {
  await codeReader.decodeFromVideoDevice(null, videoElement, callback);
} catch (err) {
  console.error('Failed to start scanner:', err);
}

HTTPS Requirement

Modern browsers require HTTPS for camera access (except on localhost):

import { BrowserQRCodeReader } from '@zxing/library';

// Check if HTTPS is required
function checkSecureContext(): boolean {
  if (!window.isSecureContext && location.hostname !== 'localhost') {
    console.error('Camera access requires HTTPS');
    alert('Camera access requires HTTPS. Please use https:// or localhost.');
    return false;
  }
  return true;
}

// Use before attempting camera access
async function startScanner() {
  if (!checkSecureContext()) {
    return;
  }

  const codeReader = new BrowserQRCodeReader();
  
  try {
    await codeReader.decodeOnceFromVideoDevice(undefined, 'video');
  } catch (err: any) {
    if (err.name === 'SecurityError') {
      alert('HTTPS is required for camera access');
    } else {
      console.error('Scanner error:', err);
    }
  }
}

Complete Example with Error Handling

Production-ready scanner implementation:

import {
  BrowserMultiFormatReader,
  Result,
  Exception,
  NotFoundException,
  DecodeHintType,
  BarcodeFormat,
  DecodeContinuouslyCallback
} from '@zxing/library';

interface ScannerOptions {
  formats?: BarcodeFormat[];
  scanInterval?: number;
  tryHarder?: boolean;
}

class ProductionScanner {
  private codeReader: BrowserMultiFormatReader | null = null;
  private isScanning: boolean = false;
  private lastDecoded: string | null = null;

  constructor(private options: ScannerOptions = {}) {}

  async initialize(): Promise<void> {
    // Check browser support
    if (!BrowserCodeReader.isMediaDevicesSupported()) {
      throw new Error('MediaDevices not supported');
    }

    if (!BrowserCodeReader.canEnumerateDevices()) {
      throw new Error('Device enumeration not supported');
    }

    // Create reader with hints
    const hints = new Map<DecodeHintType, any>();
    
    if (this.options.formats) {
      hints.set(DecodeHintType.POSSIBLE_FORMATS, this.options.formats);
    }
    
    if (this.options.tryHarder) {
      hints.set(DecodeHintType.TRY_HARDER, true);
    }

    this.codeReader = new BrowserMultiFormatReader(
      hints,
      this.options.scanInterval || 500
    );
  }

  async listCameras(): Promise<MediaDeviceInfo[]> {
    if (!this.codeReader) {
      throw new Error('Scanner not initialized');
    }

    return this.codeReader.listVideoInputDevices();
  }

  async startContinuousScanning(
    deviceId: string | null,
    videoElement: HTMLVideoElement,
    onResult: (text: string, format: BarcodeFormat) => void,
    onError?: (error: Error) => void
  ): Promise<void> {
    if (!this.codeReader) {
      throw new Error('Scanner not initialized');
    }

    if (this.isScanning) {
      throw new Error('Scanner is already running');
    }

    this.isScanning = true;
    this.lastDecoded = null;

    const callback: DecodeContinuouslyCallback = (result, error) => {
      if (!this.isScanning) return;

      if (result) {
        const text = result.getText();
        
        // Avoid duplicate processing
        if (text !== this.lastDecoded) {
          this.lastDecoded = text;
          onResult(text, result.getBarcodeFormat());
        }
      }

      if (error && !(error instanceof NotFoundException)) {
        console.error('Decode error:', error);
        if (onError) {
          onError(new Error(error.message));
        }
      }
    };

    try {
      await this.codeReader.decodeFromVideoDevice(deviceId, videoElement, callback);
    } catch (err: any) {
      this.isScanning = false;
      throw err;
    }
  }

  stopScanning(): void {
    if (!this.codeReader) return;

    this.isScanning = false;
    this.lastDecoded = null;
    this.codeReader.stopContinuousDecode();
    this.codeReader.reset();
  }

  async scanImage(image: string | HTMLImageElement): Promise<{text: string, format: BarcodeFormat} | null> {
    if (!this.codeReader) {
      throw new Error('Scanner not initialized');
    }

    try {
      const result: Result = typeof image === 'string'
        ? await this.codeReader.decodeFromImageUrl(image)
        : await this.codeReader.decodeFromImageElement(image);

      return {
        text: result.getText(),
        format: result.getBarcodeFormat()
      };
    } catch (error) {
      console.error('Image scan failed:', error);
      return null;
    }
  }

  dispose(): void {
    this.stopScanning();
    this.codeReader = null;
  }
}

// Usage example
async function setupScanner() {
  const scanner = new ProductionScanner({
    formats: [BarcodeFormat.QR_CODE, BarcodeFormat.EAN_13],
    scanInterval: 300,
    tryHarder: false
  });

  try {
    // Initialize
    await scanner.initialize();

    // List cameras
    const cameras = await scanner.listCameras();
    console.log(`Found ${cameras.length} cameras`);

    // Get video element
    const videoElement = document.getElementById('video') as HTMLVideoElement;

    // Start scanning
    await scanner.startContinuousScanning(
      null, // Use default camera
      videoElement,
      (text, format) => {
        console.log(`Scanned ${format}: ${text}`);
        document.getElementById('result')!.textContent = text;
      },
      (error) => {
        console.error('Scanner error:', error);
      }
    );

  } catch (err: any) {
    console.error('Setup failed:', err);
    
    if (err.name === 'NotAllowedError') {
      alert('Camera permission denied');
    } else if (err.name === 'NotFoundError') {
      alert('No camera found');
    } else {
      alert('Setup failed: ' + err.message);
    }
  }

  // Cleanup on page unload
  window.addEventListener('beforeunload', () => {
    scanner.dispose();
  });

  return scanner;
}

setupScanner().catch(console.error);

Troubleshooting

Common Issues

  1. Camera not starting: Ensure HTTPS (or localhost), check permissions, verify camera not in use
  2. Black screen: Video element may need explicit dimensions in CSS
  3. Performance issues: Increase timeBetweenScansMillis, reduce video resolution, limit POSSIBLE_FORMATS
  4. Continuous scanning not stopping: Always call both stopContinuousDecode() and reset()
  5. Memory leaks: Ensure reset() is called when done, especially in single-page applications

Browser Compatibility

// Feature detection
function checkBrowserSupport(): {
  mediaDevices: boolean;
  enumerate: boolean;
  getUserMedia: boolean;
} {
  return {
    mediaDevices: BrowserCodeReader.isMediaDevicesSupported(),
    enumerate: BrowserCodeReader.canEnumerateDevices(),
    getUserMedia: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)
  };
}

const support = checkBrowserSupport();
console.log('Browser support:', support);

if (!support.mediaDevices) {
  console.error('MediaDevices API not available');
}

Related Documentation

  • Core API - Core Reader/Writer interfaces
  • Image Processing - HTMLCanvasElementLuminanceSource details
  • Types and Enums - BarcodeFormat, DecodeHintType
  • Error Handling - Exception types

Install with Tessl CLI

npx tessl i tessl/npm-zxing--library

docs

index.md

tile.json