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

capacitor-community-multilens-camerapreview

v8.4.0

Published

fork of capacitor community camera preview with support for switchting lenses

Readme

Capacitor Camera Preview Multi-Lens

Capacitor camera preview plugin with native multi-lens support for iOS and Android.

This fork is focused on real-world camera UX rather than a generic preview only. It adds native-feeling rear camera zoom behavior, direct lens targeting, hardware shutter integration, zoom state events, and smoother multi-lens handling across devices that expose either true physical cameras or only logical cameras.

Highlights

  • Native multi-lens switching for rear and front cameras.
  • Use a numeric zoom value or a specific lens ID with the same setZoom() API.
  • zoomChanged events with the active lens, global zoom factor, per-lens zoom factor, and a lensChanged flag.
  • Hardware shutter integration.
  • Android: volume up and dedicated camera keys can trigger native capture.
  • iOS: native hardware camera controls are surfaced when available.
  • Faster native capture paths and lower-latency repeated captures.
  • Android logical-camera support with synthesized rear lens presets when vendors hide physical cameras behind a single logical camera.
  • Pinch-to-zoom, tap-to-focus, flash modes, front/rear flip, and preview opacity.
  • Angular service included out of the box.

Installation

npm install capacitor-community-multilens-camerapreview
npx cap sync

Exported API

The package exports:

  • CameraPreviewMultiLens for direct Capacitor plugin usage.
  • CameraPreviewService for Angular apps.
  • All TypeScript types from src/definitions.ts.

Quick Start

Direct Capacitor Usage

import {
  CameraPreviewMultiLens,
  type CameraLens,
  type ZoomChangedEvent,
} from 'capacitor-community-multilens-camerapreview';

let lenses: CameraLens[] = [];

await CameraPreviewMultiLens.start({
  position: 'rear',
  toBack: true,
  enableZoom: true,
  quality: 'medium',
  imageScale: 'fill',
  storeToFile: false,
});

const lensResponse = await CameraPreviewMultiLens.getSupportedZoomLevels();
lenses = lensResponse.result;

await CameraPreviewMultiLens.addListener('zoomChanged', (event: ZoomChangedEvent) => {
  console.log('Active lens:', event.lens.name);
  console.log('Global zoom:', event.zoomFactor);
  console.log('Current lens zoom:', event.lensZoomFactor);
  console.log('Lens changed:', event.lensChanged);
});

const photo = await CameraPreviewMultiLens.capture({ quality: 90 });
const imageSrc = `data:image/jpeg;base64,${photo.value}`;

Angular Service Usage

import { Component, OnDestroy, OnInit } from '@angular/core';
import {
  CameraPreviewService,
  type CameraLens,
  type HardwareShutterEvent,
  type ZoomChangedEvent,
} from 'capacitor-community-multilens-camerapreview';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-camera',
  templateUrl: './camera.component.html',
})
export class CameraComponent implements OnInit, OnDestroy {
  lenses: CameraLens[] = [];
  activeZoom = 1;
  private subscriptions = new Subscription();

  constructor(private cameraPreview: CameraPreviewService) {}

  async ngOnInit(): Promise<void> {
    await this.cameraPreview.start({
      position: 'rear',
      toBack: true,
      enableZoom: true,
      quality: 'medium',
      imageScale: 'fill',
      storeToFile: false,
    });

    this.lenses = await this.cameraPreview.getLenses();

    this.subscriptions.add(
      this.cameraPreview.zoomChanged$.subscribe((event: ZoomChangedEvent) => {
        this.activeZoom = event.zoomFactor;
      })
    );

    this.subscriptions.add(
      this.cameraPreview.hardwareShutterPressed$.subscribe((event: HardwareShutterEvent) => {
        if (event.error) {
          console.error('Hardware shutter error:', event.error);
          return;
        }

        if (event.value) {
          const src = event.storeToFile
            ? event.value
            : `data:image/jpeg;base64,${event.value}`;
          console.log('Native hardware shutter result:', src);
        }
      })
    );
  }

  async selectLens(lens: CameraLens): Promise<void> {
    await this.cameraPreview.setZoom(lens.id);
  }

  async setNumericZoom(zoom: number): Promise<void> {
    await this.cameraPreview.setZoom(zoom);
  }

  async takePhoto(): Promise<void> {
    const value = await this.cameraPreview.capture({ quality: 90 });
    console.log('Captured image:', value);
  }

  async ngOnDestroy(): Promise<void> {
    this.subscriptions.unsubscribe();
    await this.cameraPreview.stop();
  }
}

Lens Selection and Zoom

The plugin supports two different setZoom() modes:

1. Pass a Lens ID

Use a lens ID from getSupportedZoomLevels() when you want a deterministic lens target such as ultra-wide, main, or telephoto.

const { result: lenses } = await CameraPreviewMultiLens.getSupportedZoomLevels();
const ultraWide = lenses.find((lens) => lens.zoomFactor < 1);

if (ultraWide) {
  await CameraPreviewMultiLens.setZoom({ zoomFactor: ultraWide.id });
}

2. Pass a Numeric Zoom

Use a numeric zoom factor when you want continuous zoom behavior.

await CameraPreviewMultiLens.setZoom({ zoomFactor: 1.8 });
await CameraPreviewMultiLens.setZoom({ zoomFactor: 2.5 });

How Native Zoom Behaves

  • iOS uses native rear camera behavior so zoom transitions feel closer to the stock camera experience.
  • Android switches between real lenses when available.
  • On Android devices that only expose a single logical rear camera through CameraX, the plugin synthesizes reachable presets like 2x and 3x so your UI can still present lens buttons.
  • zoomChanged is emitted after pinch gestures, lens changes, and native zoom updates.

Lens Picker UI Example

const { result: lenses } = await CameraPreviewMultiLens.getSupportedZoomLevels();

for (const lens of lenses) {
  console.log(lens.id, lens.name, lens.zoomFactor, lens.focalLength);
}

// Example button click handler
async function onLensSelected(lensId: string): Promise<void> {
  await CameraPreviewMultiLens.setZoom({ zoomFactor: lensId });
}

Tap To Focus

Tap-to-focus is handled natively by the preview layer. No extra JavaScript API call is required.

Hardware Shutter Events

The plugin emits hardwareShutterPressed when a native hardware shutter path is used.

Event Shape

interface HardwareShutterEvent {
  source: 'volume-button' | 'camera-button' | 'camera-control';
  value?: string;
  storeToFile?: boolean;
  error?: string;
}

Notes

  • value contains the capture result when the native side already completed a photo capture.
  • If storeToFile is true, value is a file URI.
  • Otherwise value is base64 JPEG data.
  • error is populated when the native hardware shutter capture failed.

Example

await CameraPreviewMultiLens.addListener('hardwareShutterPressed', (event) => {
  if (event.error) {
    console.error(event.error);
    return;
  }

  if (!event.value) {
    return;
  }

  const output = event.storeToFile
    ? event.value
    : `data:image/jpeg;base64,${event.value}`;

  console.log('Hardware shutter capture:', output, event.source);
});

Zoom State Events

zoomChanged gives you the active lens and zoom state so you can keep your UI in sync with native pinch gestures and automatic lens transitions.

interface ZoomChangedEvent {
  lens: CameraLens;
  zoomFactor: number;
  lensZoomFactor: number;
  lensChanged: boolean;
}

Example UI Sync

await CameraPreviewMultiLens.addListener('zoomChanged', (event) => {
  activeLensId = event.lens.id;
  displayedZoom = event.zoomFactor;
  isLensTransition = event.lensChanged;
});

Capture Examples

Base64 Capture

const result = await CameraPreviewMultiLens.capture({ quality: 85 });
const src = `data:image/jpeg;base64,${result.value}`;

File Capture

await CameraPreviewMultiLens.start({
  position: 'rear',
  enableZoom: true,
  storeToFile: true,
});

const result = await CameraPreviewMultiLens.capture({ quality: 90 });
console.log('File URI:', result.value);

Low-Resolution Sample Capture

const sample = await CameraPreviewMultiLens.captureSample({ quality: 40 });

API Reference

Types

CameraPreviewOptions

interface CameraPreviewOptions {
  parent?: string;
  className?: string;
  width?: number;
  height?: number;
  x?: number;
  y?: number;
  toBack?: boolean;
  paddingBottom?: number;
  rotateWhenOrientationChanged?: boolean;
  position?: 'rear' | 'front' | string;
  quality?: 'low' | 'medium' | 'high' | 'ultra';
  imageScale?: 'fill' | 'fit';
  storeToFile?: boolean;
  disableExifHeaderStripping?: boolean;
  enableHighResolution?: boolean;
  disableAudio?: boolean;
  lockAndroidOrientation?: boolean;
  enableOpacity?: boolean;
  enableZoom?: boolean;
  zoomFactor?: string | number;
}

Important fields:

  • position: 'rear' or 'front'.
  • quality: image quality tier (low/medium/high/ultra) that drives JPEG quality and capture behavior.
  • imageScale: 'fill' to crop and fill the container or 'fit' to letterbox and show the full sensor frame.
  • enableZoom: enables native pinch-to-zoom handling.
  • zoomFactor: accepts either a numeric zoom or a lens ID string.
  • storeToFile: changes the default capture result mode.
  • toBack: shows the native preview behind your web UI on iOS and Android.

CameraPreviewPictureOptions

interface CameraPreviewPictureOptions {
  height?: number;
  width?: number;
  quality?: number;
  storeToFile?: boolean;
}

CameraLens

interface CameraLens {
  id: string;
  name: string;
  facing: 'front' | 'rear';
  zoomFactor: number;
  focalLength?: number;
}

Methods

start(options)

Starts the native camera preview.

await CameraPreviewMultiLens.start({
  position: 'rear',
  toBack: true,
  enableZoom: true,
});

stop()

Stops the preview and releases native camera resources.

capture(options)

Captures a photo and returns:

  • base64 JPEG by default.
  • file URI when storeToFile is enabled.

captureSample(options)

Captures a lower-cost sample frame for scanning or analysis.

getSupportedZoomLevels()

Returns lens descriptors for your zoom UI.

const { result } = await CameraPreviewMultiLens.getSupportedZoomLevels();

setZoom({ zoomFactor })

Accepts either:

  • a number such as 1.5 or 2.0.
  • a lens ID string returned by getSupportedZoomLevels().

getSupportedFlashModes()

Returns the flash modes available for the active camera.

setFlashMode({ flashMode })

getAvailableResolutions()

Returns the pixel size and JPEG compression quality that will be used at each image quality tier on the current device.

const { result } = await CameraPreviewMultiLens.getAvailableResolutions();
console.log(result.low, result.medium, result.high, result.ultra);

setImageScale({ imageScale })

Controls how the preview content is fitted into its container:

  • fill: default; crops to fill the view.
  • fit: letterboxes to keep the full sensor frame visible (black bars where needed).

Sets the flash mode for the current camera.

Supported values in the shared type:

  • off
  • on
  • auto
  • red-eye
  • torch

Native support depends on platform and active camera.

flip()

Switches between the front and rear camera.

setOpacity({ opacity })

Adjusts preview transparency.

  • Android: supported.
  • Web: supported.
  • iOS: do not rely on this.

resume()

Restarts or resumes the preview after returning from the background.

Angular Service Reference

CameraPreviewService wraps the Capacitor plugin and exposes RxJS streams.

Observables

  • isCameraActive$
  • errors$
  • hardwareShutterPressed$
  • zoomChanged$

Service Methods

  • start(options)
  • stop()
  • capture(options)
  • captureSample(options)
  • setFlashMode(flashMode)
  • setZoom(zoomFactor)
  • setOpacity(opacity)
  • flip()
  • getLenses()
  • getSupportedFlashModes()
  • resume()

Additional helpers:

  • getAvailableResolutions() – wrapper around the plugin method, returns the per-tier resolution/quality map or null on error.
  • setImageScale(imageScale) – updates preview scaling (fill vs fit) at runtime.

Platform Notes

iOS

  • Rear camera zoom is tuned to behave more like the native camera app.
  • High-resolution capture remains supported.
  • Hardware shutter and camera-control integration are surfaced when the device supports them.

Android

  • Works with devices that expose true physical lenses and with devices that only expose logical rear cameras.
  • If the vendor camera stack hides physical rear lenses, the plugin can still expose synthetic reachable presets so your UI is not limited to a single 1x button.
  • Pinch-to-zoom is native and optimized for continuous gesture updates.
  • Volume-up and dedicated camera keys can trigger native capture.

Web

  • Basic preview and capture are supported.
  • Lens switching, flash control, and native zoom are not implemented on web.

UI Layering Notes

If you use toBack: true, your web layer must be transparent.

For Ionic apps, that usually means making the page background transparent:

html,
body,
ion-app,
ion-content {
  --background: transparent;
  background: transparent;
}

Practical Recommendations

  • Use getSupportedZoomLevels() to build the lens picker UI.
  • Subscribe to zoomChanged so your selected lens button stays in sync with pinch gestures.
  • Prefer lens IDs for discrete buttons like .5x, 1x, 2x, and 3x.
  • Prefer numeric setZoom() for sliders and gesture-driven zoom.
  • Enable storeToFile if you do not want large base64 payloads crossing the bridge.

Known Behavior

  • Available lenses vary by vendor and device.
  • On some Android phones, telephoto and ultra-wide entries may be synthesized from reachable camera metadata instead of coming from directly bindable camera IDs.
  • Flash support depends on the currently active camera and vendor capabilities.

License

MIT