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" Effects="effects" 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>
<button @onclick="@(() => effects = [CameraEffects.Comic])">Comic</button><button @onclick="@(() => effects = [CameraEffects.Pixelate])">Pixelate</button><button @onclick="@(() => effects = null)">Clear effect</button>
@code { CameraView? camera; CameraAnalyzer? analyzer = new BarcodeAnalyzer(); // null = no analysis CameraFilter filter = CameraFilter.None;
// Effects is a parameter, not MAUI's live IList — assign a new list rather than mutating in place IReadOnlyList<ICameraEffect>? effects; 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) |
Effects |
IReadOnlyList<ICameraEffect>? |
null |
The effects chain, applied after Filter. A parameter, not MAUI’s live IList — assign a new list to change it |
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 and Effects update 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). - Effects run the full built-in set. The twelve colour grades and
CameraEffects.Blurare CSS shorthands;Comic,Sketch,PosterizeandPixelateare emitted as SVG<filter>definitions injected into the document and referenced asurl(#id)from the same chain, so they compose with the CSS ones in order. Both forms are baked intoCapturePhotoAsyncstills. WebKit’surl()support on a live<video>is historically unreliable — callCameraView.GetEffectSupport(effect)and prefer the plain-CSS looks if Safari matters. - Recording uses
MediaRecorder; output is typically WebM.
For barcode generation/rendering (as opposed to scanning), see Barcodes & QR Codes.


