npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mleonard9/vin-scanner

v1.3.0

Published

High-performance VIN scanner for React Native Vision Camera powered by Google ML Kit barcode + text recognition.

Readme

@mleonard9/vin-scanner

High-performance VIN detection for React Native powered by Google ML Kit barcode + text recognition and react-native-vision-camera.

Compiled from a combination of other community frame processing plugins

Requirements

  • react-native-vision-camera >= 3.9.0
  • react-native-worklets-core >= 0.4.0
  • iOS 13+ / Android 21+

Installation

yarn add @mleonard9/vin-scanner
# or
npm install @mleonard9/vin-scanner

# iOS
cd ios && pod install

Usage

import React, { useMemo, useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { useCameraDevice } from 'react-native-vision-camera';
import {
  Camera as VinScannerCamera,
  useVinScanner,
  type VinCandidate,
} from '@mleonard9/vin-scanner';

export function VinScannerExample(): JSX.Element {
  const device = useCameraDevice('back');
  const [results, setResults] = useState<VinCandidate[] | null>(null);

  const options = useMemo(
    () => ({
      barcode: { formats: ['code-39', 'code-128', 'pdf-417'] },
      onResult: (candidates, event) => {
        setResults(candidates);
        console.log(`Scan took ${event.duration}ms`);
      },
    }),
    []
  );

  const { frameProcessor } = useVinScanner(options);

  if (device == null) {
    return null;
  }

  return (
    <View style={StyleSheet.absoluteFill}>
      <VinScannerCamera
        style={StyleSheet.absoluteFill}
        device={device}
        frameProcessor={frameProcessor}
        callback={(event) => setResults(event.candidates)}
      />
    </View>
  );
}

Every frame, the camera runs ML Kit barcode + text recognition, extracts 17-character VIN candidates, validates them (checksum included), and routes a payload to callback.

Advanced Features

AR Overlay with Confidence Scoring

The package includes an optional AR overlay component that renders real-time bounding boxes around detected VINs, color-coded by confidence score.

Installation:

yarn add @shopify/react-native-skia
# or
npm install @shopify/react-native-skia

Usage:

import { VinScannerOverlay } from '@mleonard9/vin-scanner';

export function VinScannerWithOverlay() {
  const [candidates, setCandidates] = useState<VinCandidate[]>([]);
  
  const { frameProcessor } = useVinScanner({
    onResult: (detectedCandidates) => {
      setCandidates(detectedCandidates);
    },
  });

  return (
    <View style={StyleSheet.absoluteFill}>
      <Camera
        device={device}
        frameProcessor={frameProcessor}
        style={StyleSheet.absoluteFill}
      />
      <VinScannerOverlay 
        candidates={candidates}
        colors={{ high: '#00FF00', medium: '#FFFF00', low: '#FF0000' }}
      />
    </View>
  );
}

Confidence Scoring:

Each VinCandidate includes a confidence score (0.0-1.0) calculated from:

  • Source reliability: Barcodes score higher than OCR text (+0.3)
  • Text precision: Element-level text scores higher than block-level (+0.2)
  • Context awareness: VIN prefixes like "VIN:" increase confidence (+0.2)
  • Checksum validation: All candidates pass ISO 3779 validation (+0.2)

Overlay colors by confidence:

  • 🟢 Green (confidence > 0.8): High confidence
  • 🟡 Yellow (confidence 0.5-0.8): Medium confidence
  • 🔴 Red (confidence < 0.5): Low confidence

Smart Duplicate Filtering

By default, the scanner uses time-based debouncing to prevent duplicate callbacks for the same VIN:

const { frameProcessor } = useVinScanner({
  duplicateDebounceMs: 1500, // Default: 1500ms
  onResult: (candidates) => {
    // Only called when a new VIN is detected or after debounce period
    console.log('New VIN detected:', candidates[0]?.value);
  },
});

This prevents callback spam when holding the camera steady on a VIN, improving UX in fast-paced scanning scenarios.

Performance Telemetry

Every VinScannerEvent includes detailed performance metrics for data-driven optimization:

const { frameProcessor } = useVinScanner({
  onResult: (candidates, event) => {
    if (event.performance) {
      console.log('Performance breakdown:');
      console.log(`  Barcode scan: ${event.performance.barcodeMs}ms`);
      console.log(`  Text recognition: ${event.performance.textMs}ms`);
      console.log(`  Validation: ${event.performance.validationMs}ms`);
      console.log(`  Total: ${event.performance.totalMs}ms`);
    }
  },
});

Use these metrics to:

  • Identify performance bottlenecks (barcode vs text recognition)
  • Optimize textScanInterval based on actual timing
  • Monitor performance across different devices
  • Track improvements after configuration changes

Camera Settings Optimization

Configure camera parameters for device-specific optimization:

const { frameProcessor } = useVinScanner({
  cameraSettings: {
    fps: 60,                           // Higher FPS for smoother scanning
    lowLightBoost: true,               // Auto-boost in low light (default)
    videoStabilizationMode: 'standard' // Reduce motion blur
  },
  onResult: (candidates) => {
    console.log('Detected:', candidates[0]?.value);
  },
});

Available settings:

  • fps: Target frame rate (15-60). Higher = smoother but more CPU. Default: 30
  • lowLightBoost: Auto-brighten in dark conditions. Default: true
  • videoStabilizationMode: 'off' | 'standard' | 'cinematic' | 'auto'. Default: 'off'

Tip: For auction lanes with good lighting, try fps: 60 and videoStabilizationMode: 'standard' for best results.

Callback payload

type VinScannerEvent = {
  timestamp: number;
  duration: number;
  candidates: VinCandidate[];
  firstCandidate?: VinCandidate | null;
  raw: {
    barcodes: BarcodeDetection[];
    textBlocks: TextDetection[];
  };
};

VinCandidate contains { value, source: 'barcode' | 'text', confidence, boundingBox }.
The candidates array contains every potential VIN found in the frame. firstCandidate is a convenience reference to the best match.

Options

| Path | Type | Description | Default | | --- | --- | --- | --- | | options.barcode.enabled | boolean | Enable barcode scanning | true | | options.barcode.formats | BarcodeFormat[] | Restrict ML Kit formats ('code-39', 'code-128', 'pdf-417', etc.) | ['all'] | | options.text.enabled | boolean | Enable text recognition | true | | options.text.language | 'latin' \| 'chinese' \| 'devanagari' \| 'japanese' \| 'korean' | ML Kit language pack | 'latin' | | options.detection.textScanInterval | number | Run text recognition every Nth frame (1 = every frame) | 3 | | options.detection.maxFrameRate | number | Max FPS budget for frame processing (drops surplus frames to avoid blocking) | 30 | | options.detection.forceOrientation | 'portrait' \| 'portrait-upside-down' \| 'landscape-left' \| 'landscape-right' | Forces ML Kit to interpret every frame using the given orientation (useful when the UI is locked to portrait but the sensor reports landscape) | null | | options.detection.scanRegion | ScanRegion | Restrict ML Kit processing to a specific region of the frame (normalized coordinates 0.0-1.0). Significantly improves performance by ignoring irrelevant areas. | { x: 0.15, y: 0.15, width: 0.7, height: 0.7 } | | options.detection.enableFrameQualityCheck | boolean | Enable intelligent frame quality checks to skip blurry or dark frames, improving accuracy | true | | options.duplicateDebounceMs | number | Time in milliseconds to suppress duplicate VIN callbacks for the same value | 1500 | | options.showOverlay | boolean | Enable AR overlay (requires @shopify/react-native-skia) | false | | options.overlayColors | OverlayColors | Custom colors for AR overlay: { high, medium, low } | { high: '#00FF00', medium: '#FFFF00', low: '#FF0000' } | | options.cameraSettings | CameraSettings | Camera configuration: { fps, lowLightBoost, videoStabilizationMode } | { fps: 30, lowLightBoost: true, videoStabilizationMode: 'off' } | | options.onResult | (candidates, event) => void | Convenience callback when using useVinScanner; receives all candidates and the raw event | undefined |

Performance

Phase 1 optimizations dramatically improve scanning performance through native ROI (Region of Interest) frame cropping:

| Configuration | Avg Duration | Improvement | | --- | --- | --- | | Full frame, every frame | ~180ms | baseline | | ROI scanning (70% center) | ~95ms | 47% faster | | ROI + text interval (3 frames) | ~45ms | 75% faster | | ROI + quality check + throttle | ~30ms | 83% faster |

Default configuration uses ROI scanning (scanRegion: { x: 0.15, y: 0.15, width: 0.7, height: 0.7 }), text scan interval of 3, and frame quality checks enabled. This provides excellent accuracy while maintaining real-time performance on mid-range devices.

Tip: For challenging lighting or distance scenarios, set textScanInterval: 1 to scan every frame at the cost of higher CPU usage.

Custom scan regions:

const { frameProcessor } = useVinScanner({
  detection: {
    // Focus on center 50% of frame
    scanRegion: { x: 0.25, y: 0.25, width: 0.5, height: 0.5 },
    textScanInterval: 2,
  },
  onResult: (candidates) => {
    console.log('Detected VINs:', candidates);
  },
});

Advanced frame-processor controls

  • Per-frame plugin overrides: both barcode and text frame processor plugins accept per-frame arguments, so you can dynamically change ML Kit barcode formats or text recognition language without reinitializing the plugin. Call barcodeScanner.scanBarcodes(frame, { 'pdf-417': true }) or textScanner.scanText(frame, { language: 'japanese' }) inside your worklet to override the resolved defaults for a single frame.
  • Orientation overrides: If your UI is locked to portrait (e.g., iPad kiosks) but VisionCamera streams landscape buffers, set detection.forceOrientation: 'portrait'. The JS hook forwards that override to the native plugins so ML Kit always interprets frames with the requested rotation, eliminating the “upside-down unless I flip the paper” problem described in the VisionCamera orientation guide.
  • Shared bounding boxes: native plugins now stream bounding box coordinates via zero-copy shared arrays, minimizing JSI serialization. The hook translates these buffers into the familiar BoundingBox structures before running VIN heuristics, so no API change is required.
  • Orientation-safe processing: the native plugins forward VisionCamera’s frame orientation metadata directly into ML Kit as recommended in the VisionCamera orientation guide, ensuring portrait VIN scans stay upright.

Hook-only usage

If you prefer to configure react-native-vision-camera yourself, grab the frame processor from the hook:

const { frameProcessor } = useVinScanner({
  onResult: (candidates, event) => {
    console.log('Current VINs', candidates, event.firstCandidate);
    console.log(`Duration: ${event.duration}ms`);
  },
});

return (
  <Camera
    ref={cameraRef}
    device={device}
    frameProcessor={frameProcessor}
    pixelFormat="yuv"
    style={StyleSheet.absoluteFill}
  />
);

Publishing (internal use)

This package is scoped (@mleonard9/vin-scanner). To release a new build:

yarn prepare   # builds /lib via bob
npm publish --access public

Ensure the authenticated npm user has access to the @mleonard9 scope.