Skip to content
Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!

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"
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)
}
}
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.

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).
  • Effects run the full built-in set. The twelve colour grades and CameraEffects.Blur are CSS shorthands; Comic, Sketch, Posterize and Pixelate are emitted as SVG <filter> definitions injected into the document and referenced as url(#id) from the same chain, so they compose with the CSS ones in order. Both forms are baked into CapturePhotoAsync stills. WebKit’s url() support on a live <video> is historically unreliable — call CameraView.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.