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 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.

  • NuGet downloads for Shiny.Blazor.Controls.Camera
Terminal window
dotnet add package Shiny.Blazor.Controls.Camera
@using Shiny.Blazor.Controls.Camera
@using Shiny.Controls.Camera

getUserMedia requires a secure context — HTTPS, or localhost during 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)
}
}
ParameterTypeDefaultDescription
FacingCameraFacingBackFront maps to the browser "user" facing mode
CameraIdstring?nullExact device id from GetAvailableCamerasAsync(); overrides Facing when set
AnalyzerCameraAnalyzer?nullThe frame analyzer (assign a BarcodeAnalyzer; null = no analysis). Base type carries ScanWindow (RectF?) and ShowBoundingBox (bool)
ShowOverlaybooltrueDraw overlay boxes on the overlay canvas
AutoStartbooltrueStart the preview on first render
FilterCameraFilterNoneLive CSS filter on the <video> element (also baked into CapturePhotoAsync stills)
CssClass / Stylestring?nullHost element styling
BarcodesDetectedEventCallback<IReadOnlyList<CameraBarcode>>Every code decoded in the frame (each Format, Value, normalized box) — gated: fires only while a RequestBarcodeAsync is outstanding
OverlaysChangedEventCallback<IReadOnlyList<OverlayBox>>Latest styled overlay boxes (presentation only)
OnErrorEventCallback<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.

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.CameraId
  • Barcode scanning uses the native BarcodeDetector — available in Chromium browsers (Chrome / Edge). On unsupported browsers (Firefox / Safari) the component raises OnError once and the preview keeps running; slot in a ZXing-js fallback if you need universal coverage.
  • Filters map to CSS filter strings, 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.