TypeScript port of ZXing multi-format 1D/2D barcode image processing library
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/browserLegacy 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.
// Legacy (deprecated)
import {
BrowserCodeReader,
BrowserMultiFormatReader,
BrowserQRCodeReader,
BrowserBarcodeReader,
BrowserAztecCodeReader,
BrowserDatamatrixCodeReader,
BrowserPDF417Reader,
BrowserQRCodeSvgWriter,
BrowserSvgCodeWriter,
HTMLCanvasElementLuminanceSource,
VideoInputDevice,
DecodeContinuouslyCallback
} from '@zxing/library';The browser module provides:
BrowserCodeReader provides common camera and video handlingBase 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);
}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);
}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();
}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();
}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);
}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();
}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);
}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);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;
}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);
}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();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;
});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`);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();
});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();
}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();
});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
}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);
}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);
}
}
}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);// 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');
}Install with Tessl CLI
npx tessl i tessl/npm-zxing--library