Skip to content
Client v5: BLE, BLE Hosting, HTTP, Jobs - Linux, MacOS, & Blazor Support! Full AOT, RX on BLE only & MANY other features! Power up!

CameraView

Shiny.Maui.Controls.Camera is a cross-platform CameraView for .NET MAUI — live preview with zoom, torch, lens selection, photo & video capture, and live color filters — backed by AVFoundation (iOS / Mac Catalyst / macOS AppKit), CameraX (Android) and Media Capture (Windows). A matching Shiny.Blazor.Controls.Camera brings the same control to Blazor WebAssembly via getUserMedia.

What sets it apart from other camera controls is the pluggable frame-analysis pipeline: assign a single analyzer to the view and it streams frames to it off the UI thread. The analyzer draws styled bounding boxes continuously (via the built-in CameraOverlayView) but delivers a result only on a gated “scan trigger” — arm with Scan() / ScanCommand and the next confirmed detection fires once (an OnDetected handler returning true keeps scanning). The analyzer can be declared right in XAML (it’s the content property of CameraView), and an optional ScanWindow restricts detection to a region and draws an aim reticle. Ships with analyzers for barcode/QR scanning, face detection, motion detection, OCR, and structured documents — invoices (with order lines), receipts (line items + per-tax breakdown + totals), business cards (emails + typed phones + name/title/company), AAMVA driver’s licenses, Canadian-province-aware health cards, credit cards, and passports (MRZ). Each document is a strong record with nullable fields.

  • NuGet downloads for Shiny.Maui.Controls.Camera
  • NuGet downloads for Shiny.Blazor.Controls.Camera
Frameworks
.NET MAUI
Blazor

No files change for this selection — pick a library above to see what it adds.

  • Live preview with AspectFill / AspectFit scaling.
  • Lens & device selectionFacing (Back / Front) or pick an exact device (multiple back lenses, USB webcams on macOS) via GetAvailableCamerasAsync() + CameraId.
  • Zoom (clamped to the device’s reported range) and torch / flashlight.
  • Photo capture → JPEG bytes, and video recording with optional audio.
  • Live color filters — Mono, Noir, Sepia, Invert, Vivid, Cool, Warm, Fade, Chrome, Instant, Tonal (Apple Core Image, Android RenderEffect, Blazor CSS).
  • Frame-analysis pipeline — a single pluggable IFrameAnalyzer with back-pressure (drop-on-busy); boxes draw every frame, results are delivered on a gated scan trigger (Scan() / OnDetected), optionally restricted to a ScanWindow. See Frame Analyzers.
  • Modular analyzer packages — add only what you need: .Camera.Barcode, .Camera.Face, .Camera.Motion, .Camera.Ocr, .Camera.Documents.
claude plugin marketplace add shinyorg/skills
claude plugin install shiny-client@shiny
BLE, GPS, Jobs, Notifications, Push, HTTP Transfers, OBD, Music, Health, DataSync — iOS, Android, Windows, MacOS, Linux, Web
claude plugin install shiny-maui@shiny
Shell, Contact Store
claude plugin install controls@shiny
TableView, BottomSheet, PillView, ImageViewer, Scheduler, Markdown, Mermaid Diagrams — MAUI and Blazor
claude plugin install shiny-mediator@shiny
Mediator/CQRS with middleware and source generators
claude plugin install shiny-data@shiny
DocumentDB and Spatial data libraries
claude plugin install shiny-aspire@shiny
Orleans and Gluetun Aspire integrations
claude plugin install shiny-extensions@shiny
DI, Stores, Reflector, Localization, Hosting modules
copilot plugin marketplace add https://github.com/shinyorg/skills
copilot plugin install shiny-client@shiny
BLE, GPS, Jobs, Notifications, Push, HTTP Transfers, OBD, Music, Health, DataSync — iOS, Android, Windows, MacOS, Linux, Web
copilot plugin install shiny-maui@shiny
Shell, Contact Store
copilot plugin install controls@shiny
TableView, BottomSheet, PillView, ImageViewer, Scheduler, Markdown, Mermaid Diagrams — MAUI and Blazor
copilot plugin install shiny-mediator@shiny
Mediator/CQRS with middleware and source generators
copilot plugin install shiny-data@shiny
DocumentDB and Spatial data libraries
copilot plugin install shiny-aspire@shiny
Orleans and Gluetun Aspire integrations
copilot plugin install shiny-extensions@shiny
DI, Stores, Reflector, Localization, Hosting modules
View Skills Repository
Terminal window
dotnet add package Shiny.Maui.Controls.Camera

Register the handler alongside UseShinyControls():

builder
.UseShinyControls()
.UseShinyCamera();
xmlns:cam="http://shiny.net/maui/camera"

Add the platform permissions your app needs:

  • iOS / Mac Catalyst / macOSNSCameraUsageDescription (and NSMicrophoneUsageDescription for video with audio) in Info.plist; the macOS camera entitlement when sandboxed.
  • Android<uses-permission android:name="android.permission.CAMERA" /> (and RECORD_AUDIO for video with audio). Minimum SDK 23 (CameraX requirement).
  • Windows — the webcam (and microphone) capability in Package.appxmanifest.
Terminal window
dotnet add package Shiny.Blazor.Controls.Camera
@using Shiny.Blazor.Controls.Camera
@using Shiny.Controls.Camera

See Blazor Usage for the WebAssembly specifics.

<cam:CameraView x:Name="Camera"
Facing="Back"
ScaleMode="AspectFill"
Zoom="1"
IsTorchOn="False"
Filter="None" />
// The preview auto-starts when the view is added (IsActive defaults true) and the control requests camera
// permission itself — handle a denial (or any error) via CameraError. Toggle IsActive for lifecycle.
this.Camera.CameraError += (_, e) => status = e.Message; // e.g. "Camera permission denied"
protected override void OnAppearing()
{
base.OnAppearing();
this.Camera.IsActive = true; // resume (no-op on first show — it already auto-started)
}
protected override void OnDisappearing()
{
base.OnDisappearing();
this.Camera.IsActive = false; // release the camera while off-screen
}
// Capture a still — the current Filter is baked into the JPEG, so preview and photo match
var photo = await this.Camera.CapturePhotoAsync();
await File.WriteAllBytesAsync(path, photo.Data);
// Flip the lens
this.Camera.Facing = this.Camera.Facing == CameraFacing.Back
? CameraFacing.Front
: CameraFacing.Back;

VideoRecordingOptions.IncludeAudio defaults to true; the audio permission is requested only when audio is requested.

await this.Camera.StartVideoRecordingAsync(new VideoRecordingOptions { IncludeAudio = true });
// ... later ...
var video = await this.Camera.StopVideoRecordingAsync(); // CameraVideo { FilePath, Duration }

On Android, CameraX allows a limited number of concurrent use-cases, so video recording and the analysis pipeline are mutually exclusive — StartVideoRecordingAsync throws a clear error while an enabled analyzer is attached. Disable the analyzer (IsEnabled = false) or clear it (CameraView.Analyzer = null) to record; the camera rebinds automatically.

Recorded video is not filtered — it records the raw feed even when a Filter is set (filters apply to the preview and captured photos only).

<cam:CameraView Filter="Noir" />

Filter accepts None, Mono, Noir, Sepia, Invert, Vivid, Cool, Warm, Fade, Chrome, Instant, Tonal. Filtering is applied to the live preview (Apple CIFilter — the Fade/Chrome/Instant/Tonal set maps to the Core Image CIPhotoEffect* filters; Android RenderEffect color matrix on API 31+; Blazor CSS); on Windows it is best-effort.

The same filter is also baked into captured photos (CapturePhotoAsync) on Apple and Android, so a still matches the preview. Recorded video is not filtered (it records the raw feed), and Windows photos are unfiltered (it has no live filter). On Android below API 31 the live preview is unfiltered (no RenderEffect), but captured photos are still filtered.

Facing picks by position, but you can enumerate every physical camera and pin an exact one — essential for phones with multiple back lenses or desktops with several webcams:

var cameras = await this.Camera.GetAvailableCamerasAsync();
// CameraInfo { Id, Name, Facing, IsDefault }
this.Camera.CameraId = cameras.First(c => c.Name.Contains("USB")).Id;

Set CameraId back to null to fall back to Facing.

PropertyTypeDefaultDescription
FacingCameraFacingBackBack / Front / External lens position
CameraIdstring?nullExact device id from GetAvailableCamerasAsync(); overrides Facing
IsActivebooltrueWhether the session is running
Zoomdouble1Zoom factor, clamped to MinZoom..MaxZoom
MinZoom / MaxZoomdouble1Supported zoom range (reported by the handler)
IsTorchOnboolfalseContinuous flashlight/torch
FlashModeCameraFlashModeOffFlash behaviour for still capture (Off/On/Auto)
ScaleModePreviewScaleModeAspectFillHow the preview fills the view
FilterCameraFilterNoneColor filter — applied to the preview and captured photos (not recorded video; no-op on Windows)
ShowDetectionOverlaybooltrueWhether overlay boxes are surfaced for the overlay
AnalyzerIFrameAnalyzer?nullThe single frame analyzer (also the XAML content property); swap live or toggle it via its IsEnabled, set null for no analysis — see Frame Analyzers
ScanWindowRectF? (read-only)nullMirrors the active analyzer’s ScanWindow (normalized 0..1; null = whole frame)
IsRecordingbool (read-only)falseWhether a recording is in progress
OverlaysIReadOnlyList<OverlayBox>emptyLatest aggregated overlay boxes (read-only)
MemberDescription
Scan() / ScanCommandArm the analyzer for one scan (the trigger)
StopScanning()Disarm the analyzer
RequestPermissionAsync()Request camera permission
StartAsync() / StopAsync()Start / stop the session
CapturePhotoAsync()Capture a still → CameraPhoto
StartVideoRecordingAsync() / StopVideoRecordingAsync()Record video → CameraVideo
GetAvailableCamerasAsync()Enumerate cameras → IReadOnlyList<CameraInfo>
MediaCaptured / VideoCapturedRaised after a photo / video is captured
OverlaysChangedRaised with the latest aggregated overlay boxes (presentation only)
CameraErrorRaised on a camera or pipeline error

For results, arm with Scan() / ScanCommand, then handle the analyzer’s OnDetected (Func<TArgs, Task<bool>>return true to keep scanning) or its typed event / Command (both gated by arming): BarcodesDetected, FacesDetected, MotionChanged, TextRecognized, DocumentDetected. Detection events that can hold several hits in one frame (barcode, face, motion) deliver an array. OverlaysChanged is only the styled boxes for drawing (never gated).

Assign the matching analyzer to CameraView.Analyzer (one runs at a time). To offer several detectors, build them up front and swap the chosen one into Analyzer (e.g. from a picker) — see Frame Analyzers.

  • Scan a barcode / QRBarcodeAnalyzer (Frame Analyzers).
  • Detect / box facesFaceAnalyzer.
  • Trigger on movementMotionAnalyzer.
  • Read raw textOcrAnalyzer (TextRecognized).
  • Parse an invoice (header + order lines) → InvoiceAnalyzer.
  • Parse a receipt (line items, per-tax breakdown, subtotal/tip/total) → ReceiptAnalyzer.
  • Scan a business card (name/title/company + emails/phones/website) → BusinessCardAnalyzer.
  • Scan a driver’s license / health cardDriversLicenseAnalyzer (deterministic AAMVA, incl. Canadian provinces + jurisdiction) or a Canadian-province-aware HealthCardAnalyzer from .Camera.Documents.
  • Scan a passportPassportAnalyzer (deterministic MRZ → number, names, nationality, DOB, expiry).
  • Read a credit cardCreditCardAnalyzer (brand + number deterministic; name/expiry best-effort).
  • Just take a photo or record video → no analyzer required; use CapturePhotoAsync / StartVideoRecordingAsync.