CameraView on Blazor
Shiny.Blazor.Controls.Camera brings the CameraView to Blazor WebAssembly using the browser’s getUserMedia / MediaRecorder / BarcodeDetector APIs. Frame analysis runs in JavaScript — only flat, named DTOs (CameraBarcode, CameraOverlayBox) cross the interop boundary — so the camera stays fast and trim-safe. You select analysis by assigning an Analyzer (a CameraAnalyzer?); only BarcodeAnalyzer has a real in-browser engine (a placeholder FaceAnalyzer reports “not supported”). Face/motion/OCR/documents remain MAUI-only.
Installation
Section titled “Installation”dotnet add package Shiny.Blazor.Controls.Camera@using Shiny.Blazor.Controls.Camera@using Shiny.Controls.Camera
getUserMediarequires a secure context — HTTPS, orlocalhostduring development.
<div style="position:relative;width:100%;max-width:480px;aspect-ratio:3/4;"> <CameraView @ref="camera" Facing="CameraFacing.Back" Analyzer="@analyzer" ShowOverlay="true" Filter="filter" BarcodesDetected="OnBarcodes" OnError="msg => status = msg" Style="width:100%;height:100%;" /></div>
<button @onclick="Scan">Scan</button><button @onclick="Capture">Photo</button><button @onclick="ToggleRecording">@(recording ? "Stop" : "Record")</button>
<select @bind="filter"> @foreach (var f in Enum.GetValues<CameraFilter>()) { <option value="@f">@f</option> }</select>
@code { CameraView? camera; CameraAnalyzer? analyzer = new BarcodeAnalyzer(); // null = no analysis CameraFilter filter = CameraFilter.None; bool recording; string? status;
// gated request/response: arm, await the NEXT decode, then go quiet. The await is the gate; // call it again in a loop to "keep scanning". Pass a CancellationToken to cancel a pending request. async Task Scan() { var code = await camera!.RequestBarcodeAsync(); // still returns the first CameraBarcode status = $"{code.Format}: {code.Value}"; // code.X/Y/W/H are normalized 0..1 }
// optional passive observer — fires with EVERY code in the frame, only while a // RequestBarcodeAsync is outstanding (gated, quiet by default) void OnBarcodes(IReadOnlyList<CameraBarcode> codes) => status = $"{codes.Count} code(s): {codes[0].Value}";
async Task Capture() { var jpeg = await camera!.CapturePhotoAsync(); // byte[] // ... use the JPEG bytes ... }
async Task ToggleRecording() { if (!recording) { await camera!.StartRecordingAsync(); recording = true; } else { var webm = await camera!.StopRecordingAsync(); recording = false; } // byte[] (WebM) }}Parameters
Section titled “Parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
Facing | CameraFacing | Back | Front maps to the browser "user" facing mode |
CameraId | string? | null | Exact device id from GetAvailableCamerasAsync(); overrides Facing when set |
Analyzer | CameraAnalyzer? | null | The frame analyzer (assign a BarcodeAnalyzer; null = no analysis). Base type carries ScanWindow (RectF?) and ShowBoundingBox (bool) |
ShowOverlay | bool | true | Draw overlay boxes on the overlay canvas |
AutoStart | bool | true | Start the preview on first render |
Filter | CameraFilter | None | Live CSS filter on the <video> element (also baked into CapturePhotoAsync stills) |
CssClass / Style | string? | null | Host element styling |
BarcodesDetected | EventCallback<IReadOnlyList<CameraBarcode>> | — | Every code decoded in the frame (each Format, Value, normalized box) — gated: fires only while a RequestBarcodeAsync is outstanding |
OverlaysChanged | EventCallback<IReadOnlyList<OverlayBox>> | — | Latest styled overlay boxes (presentation only) |
OnError | EventCallback<string> | — | Camera could not start (permission, no device, insecure context) |
Changing Facing, CameraId, Analyzer, or ShowOverlay while running re-acquires the stream automatically (they’re read when the camera starts); Filter updates live without a restart. To change scan options live (e.g. a ScanWindow band), assign a fresh BarcodeAnalyzer instance — the component restarts on the reference change. Setting BarcodeAnalyzer.ScanWindow (RectF?, normalized 0..1) restricts scanning to that region and the JS overlay dims outside it + draws a reticle.
Methods
Section titled “Methods”RequestBarcodeAsync(ct = default) → Task<CameraBarcode> (arm, resolve on the next decode, then go quiet — the gated equivalent of MAUI’s OnDetected; loop to keep scanning, pass a CancellationToken to cancel) · StartAsync() · StopAsync() · CapturePhotoAsync() → JPEG byte[] (filtered to match the preview) · StartRecordingAsync(includeAudio = true) · StopRecordingAsync() → WebM byte[] · GetAvailableCamerasAsync() → IReadOnlyList<CameraDevice> (Id, Name).
// pick a specific lens — labels populate only after the camera has started (permission granted)var cameras = await camera.GetAvailableCamerasAsync();cameraId = cameras.FirstOrDefault()?.Id; // bind to CameraView.CameraIdBrowser support
Section titled “Browser support”- Barcode scanning uses the native
BarcodeDetector— available in Chromium browsers (Chrome / Edge). On unsupported browsers (Firefox / Safari) the component raisesOnErroronce and the preview keeps running; slot in a ZXing-js fallback if you need universal coverage. - Filters map to CSS
filterstrings, so they work in every browser (temperature filters are approximate — CSS has no true white balance). - Recording uses
MediaRecorder; output is typically WebM.
For barcode generation/rendering (as opposed to scanning), see Barcodes & QR Codes.