MediaDevices
Enumerate cameras, microphones and speakers, then request a live stream and attach it straight to a video element - camera access without a line of JavaScript.
@inject Bit.Butil.MediaDevices mediaDevicesMDN reference
navigator.mediaDevices is only available in a secure context (HTTPS or localhost).
The browser prompts for camera/microphone access on the first GetUserMedia call,
and device labels from EnumerateDevices stay blank until permission for a matching
input has been granted.
Returns true when the runtime exposes navigator.mediaDevices. During prerender/SSR the check returns false rather than throwing, so defer it to OnAfterRenderAsync.
@inject Bit.Butil.MediaDevices mediaDevices
var supported = await mediaDevices.IsSupported();Lists every input/output media device as a typed MediaDeviceInfo: DeviceId, Kind (audioinput, audiooutput or videoinput), Label and GroupId. Run it again after granting permission to see the labels fill in.
var devices = await mediaDevices.EnumerateDevices();
foreach (var device in devices)
{
// device.Kind: "audioinput" | "audiooutput" | "videoinput"
// device.Label: blank until permission is granted
Console.WriteLine($"{device.Kind}: {device.Label}");
}GetUserMedia asks the user for audio and/or video access and returns a MediaStreamHandle (null when denied or unsatisfiable). AttachTo wires the stream to a video or audio element's srcObject, SetEnabled pauses or resumes every track without dropping the stream, and disposing the handle stops the hardware.
private MediaStreamHandle? _stream;
private ElementReference _preview;
_stream = await mediaDevices.GetUserMedia(audio: true, video: true);
if (_stream is not null)
{
await _stream.AttachTo(_preview); // <video @ref="_preview" autoplay playsinline muted />
await _stream.SetEnabled(false); // pause all tracks
await _stream.SetEnabled(true); // resume
await _stream.DisposeAsync(); // stop tracks, release the camera light
}The audioConstraints/videoConstraints parameters accept any MediaTrackConstraints-shaped object - anonymous C# objects serialize straight through to the browser, so you can pin a device id, request a resolution, or prefer the front camera.
// prefer the front camera at 1280x720:
var stream = await mediaDevices.GetUserMedia(
audio: false,
video: true,
videoConstraints: new
{
width = new { ideal = 1280 },
height = new { ideal = 720 },
facingMode = "user",
});
// pin an exact input from EnumerateDevices:
var mic = await mediaDevices.GetUserMedia(
audio: true,
audioConstraints: new { deviceId = new { exact = someDeviceId } });The Screen Capture API. GetDisplayMedia shows the browser's own surface picker and hands back the same MediaStreamHandle a camera does - AttachTo, SetEnabled and DisposeAsync all work the same way. Unlike a camera it grants nothing persistent: every call prompts again. The onEnded callback is the only way to hear about the user ending the share from the browser's own 'Stop sharing' bar.
IAsyncDisposable
Bit.Butil.MediaDevices mediaDevices
<video @ref="_screenPreview" autoplay playsinline muted></video>
@* Must be a user gesture: the picker is refused without one. *@
<button @onclick="Share">Share a screen</button>
{
private ElementReference _screenPreview;
private MediaStreamHandle? _screen;
private async Task Share()
{
_screen = await mediaDevices.GetDisplayMedia(
audio: false,
options: new DisplayMediaOptions
{
DisplaySurface = "monitor", // pre-select a whole screen
SelfBrowserSurface = "exclude", // don't offer this tab (avoids the hall of mirrors)
},
// The browser's own "stop sharing" bar ends it without telling the page anything else.
onEnded: () => InvokeAsync(() =>
{
_screen = null;
StateHasChanged();
}));
if (_screen is not null)
{
await _screen.AttachTo(_screenPreview);
// what the user actually picked, not what was asked for:
var settings = await mediaDevices.GetDisplaySettings(_screen);
// settings.DisplaySurface: "monitor" | "window" | "browser"
// settings.Width / Height / FrameRate
}
}
public async ValueTask DisposeAsync()
{
if (_screen is not null) await _screen.DisposeAsync();
}
}MediaStreamHandle when you are done; as a safety net, the
MediaDevices service stops any leaked streams when its scope is torn down.
API reference
ValueTask<bool> IsSupported()ValueTask<MediaDeviceInfo[]> EnumerateDevices()ValueTask<MediaStreamHandle?> GetUserMedia(bool audio = true, bool video = false, object? audioConstraints = null, object? videoConstraints = null)ValueTask<bool> IsDisplayCaptureSupported()ValueTask<MediaStreamHandle?> GetDisplayMedia(bool audio = false, DisplayMediaOptions? options = null, object? videoConstraints = null, Action? onEnded = null)ValueTask<DisplayMediaSettings?> GetDisplaySettings(MediaStreamHandle stream)string? FocusBehaviorValueTask DisposeAsync()Guid Id { get; }ValueTask AttachTo(ElementReference videoOrAudioElement)ValueTask SetEnabled(bool enabled)ValueTask DisposeAsync()