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.
No files change for this selection — pick a library above to see what it adds.
Features
Section titled “Features”- Live preview with
AspectFill/AspectFitscaling. - Lens & device selection —
Facing(Back / Front) or pick an exact device (multiple back lenses, USB webcams on macOS) viaGetAvailableCamerasAsync()+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
IFrameAnalyzerwith back-pressure (drop-on-busy); boxes draw every frame, results are delivered on a gated scan trigger (Scan()/OnDetected), optionally restricted to aScanWindow. See Frame Analyzers. - Modular analyzer packages — add only what you need:
.Camera.Barcode,.Camera.Face,.Camera.Motion,.Camera.Ocr,.Camera.Documents.
AI Skill
Section titled “AI Skill”Step 1 — Add the marketplace:
claude plugin marketplace add shinyorg/skills Step 2 — Install plugins:
claude plugin install shiny-client@shiny claude plugin install shiny-maui@shiny claude plugin install controls@shiny claude plugin install shiny-mediator@shiny claude plugin install shiny-data@shiny claude plugin install shiny-aspire@shiny claude plugin install shiny-extensions@shiny Step 1 — Add the marketplace:
copilot plugin marketplace add https://github.com/shinyorg/skills Step 2 — Install plugins:
copilot plugin install shiny-client@shiny copilot plugin install shiny-maui@shiny copilot plugin install controls@shiny copilot plugin install shiny-mediator@shiny copilot plugin install shiny-data@shiny copilot plugin install shiny-aspire@shiny copilot plugin install shiny-extensions@shiny Installation
Section titled “Installation”.NET MAUI
Section titled “.NET MAUI”dotnet add package Shiny.Maui.Controls.CameraRegister the handler alongside UseShinyControls():
builder .UseShinyControls() .UseShinyCamera();xmlns:cam="http://shiny.net/maui/camera"Add the platform permissions your app needs:
- iOS / Mac Catalyst / macOS —
NSCameraUsageDescription(andNSMicrophoneUsageDescriptionfor video with audio) inInfo.plist; the macOS camera entitlement when sandboxed. - Android —
<uses-permission android:name="android.permission.CAMERA" />(andRECORD_AUDIOfor video with audio). Minimum SDK 23 (CameraX requirement). - Windows — the
webcam(andmicrophone) capability inPackage.appxmanifest.
Blazor
Section titled “Blazor”dotnet add package Shiny.Blazor.Controls.Camera@using Shiny.Blazor.Controls.Camera@using Shiny.Controls.CameraSee Blazor Usage for the WebAssembly specifics.
Quick Start — MAUI
Section titled “Quick Start — MAUI”<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 matchvar photo = await this.Camera.CapturePhotoAsync();await File.WriteAllBytesAsync(path, photo.Data);
// Flip the lensthis.Camera.Facing = this.Camera.Facing == CameraFacing.Back ? CameraFacing.Front : CameraFacing.Back;Video Recording
Section titled “Video Recording”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 —
StartVideoRecordingAsyncthrows 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
Filteris set (filters apply to the preview and captured photos only).
Live Filters
Section titled “Live Filters”<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.
Selecting a Camera
Section titled “Selecting a Camera”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.
Properties
Section titled “Properties”| Property | Type | Default | Description |
|---|---|---|---|
Facing | CameraFacing | Back | Back / Front / External lens position |
CameraId | string? | null | Exact device id from GetAvailableCamerasAsync(); overrides Facing |
IsActive | bool | true | Whether the session is running |
Zoom | double | 1 | Zoom factor, clamped to MinZoom..MaxZoom |
MinZoom / MaxZoom | double | 1 | Supported zoom range (reported by the handler) |
IsTorchOn | bool | false | Continuous flashlight/torch |
FlashMode | CameraFlashMode | Off | Flash behaviour for still capture (Off/On/Auto) |
ScaleMode | PreviewScaleMode | AspectFill | How the preview fills the view |
Filter | CameraFilter | None | Color filter — applied to the preview and captured photos (not recorded video; no-op on Windows) |
ShowDetectionOverlay | bool | true | Whether overlay boxes are surfaced for the overlay |
Analyzer | IFrameAnalyzer? | null | The single frame analyzer (also the XAML content property); swap live or toggle it via its IsEnabled, set null for no analysis — see Frame Analyzers |
ScanWindow | RectF? (read-only) | null | Mirrors the active analyzer’s ScanWindow (normalized 0..1; null = whole frame) |
IsRecording | bool (read-only) | false | Whether a recording is in progress |
Overlays | IReadOnlyList<OverlayBox> | empty | Latest aggregated overlay boxes (read-only) |
Methods & Events
Section titled “Methods & Events”| Member | Description |
|---|---|
Scan() / ScanCommand | Arm 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 / VideoCaptured | Raised after a photo / video is captured |
OverlaysChanged | Raised with the latest aggregated overlay boxes (presentation only) |
CameraError | Raised on a camera or pipeline error |
For results, arm with
Scan()/ScanCommand, then handle the analyzer’sOnDetected(Func<TArgs, Task<bool>>—return trueto 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.OverlaysChangedis only the styled boxes for drawing (never gated).
When to Use What
Section titled “When to Use What”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 / QR →
BarcodeAnalyzer(Frame Analyzers). - Detect / box faces →
FaceAnalyzer. - Trigger on movement →
MotionAnalyzer. - Read raw text →
OcrAnalyzer(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 card →
DriversLicenseAnalyzer(deterministic AAMVA, incl. Canadian provinces + jurisdiction) or a Canadian-province-awareHealthCardAnalyzerfrom.Camera.Documents. - Scan a passport →
PassportAnalyzer(deterministic MRZ → number, names, nationality, DOB, expiry). - Read a credit card →
CreditCardAnalyzer(brand + number deterministic; name/expiry best-effort). - Just take a photo or record video → no analyzer required; use
CapturePhotoAsync/StartVideoRecordingAsync.