BarcodeDetector
Find QR codes and barcodes in a camera frame or an image, using the platform's own decoder - no JavaScript scanning library, and no canvas pixel-pushing.
@inject Bit.Butil.BarcodeDetector barcodeDetectorMDN reference
GetSupportedFormats is for.
Worth calling both before offering a scanner: asking for a format the device can't decode makes detection quietly return nothing rather than throwing, so a list you didn't check turns into a scanner that never finds anything.
@inject Bit.Butil.BarcodeDetector barcodeDetector
if (await barcodeDetector.IsSupported())
{
var formats = await barcodeDetector.GetSupportedFormats();
// "qr_code", "ean_13", "code_128", "pdf417", ...
}The usual shape: open a stream with MediaDevices, attach it to a video element, and point StartScan at that element. Scanning is a poll - there is no 'barcode appeared' event - and detection is much slower than a frame, so sampling a few times a second is both enough and cheaper than trying every frame. A tick that arrives while the previous detection is still running is skipped, so a slow device degrades to a lower rate rather than building a backlog.
private ButilSubscription? _scan;
private ElementReference _video;
_stream = await mediaDevices.GetUserMedia(
audio: false,
video: true,
videoConstraints: new { facingMode = "environment" }); // rear camera on a phone
await _stream!.AttachTo(_video);
_scan = await barcodeDetector.StartScan(
_video,
onDetected: codes => InvokeAsync(() =>
{
// fires repeatedly while a code stays in view - debounce on the value
_lastValue = codes[0].RawValue;
StateHasChanged();
}),
formats: ["qr_code"],
intervalMs: 250);
// later:
await _scan!.DisposeAsync();Detect reads one frame of a video, image or canvas element. DetectImage takes an encoded image's bytes - a PNG or JPEG, not raw pixels - which is what you want for a file the user picked or an image you fetched. The decoded bitmap is released as soon as the scan finishes; it holds uncompressed pixels, which would be a real leak in a loop.
// one frame of whatever the video is showing right now:
var codes = await barcodeDetector.Detect(_video, formats: ["qr_code"]);
// or an image the user picked:
var bytes = await fileReader.ReadAsByteArray(inputElement, index: 0);
var found = await barcodeDetector.DetectImage(bytes, "image/png");
foreach (var code in found)
{
// code.RawValue, code.Format, and code.X/Y/Width/Height in source pixels
}IsSupported up front rather than inferring it from silence.
API reference
ValueTask<bool> IsSupported()ValueTask<string[]> GetSupportedFormats()ValueTask<DetectedBarcode[]> Detect(ElementReference element, string[]? formats = null)ValueTask<DetectedBarcode[]> DetectImage(byte[] imageBytes, string mimeType = "image/png", string[]? formats = null)ValueTask<ButilSubscription?> StartScan(ElementReference element, Action<DetectedBarcode[]> onDetected, string[]? formats = null, int intervalMs = 250)ValueTask DisposeAsync()RawValue, Format, X, Y, Width, Height