loading
Note:
Permission and secure contextnavigator.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.

Support check

IsSupported

Returns true when the runtime exposes navigator.mediaDevices. During prerender/SSR the check returns false rather than throwing, so defer it to OnAfterRenderAsync.

C#
@inject Bit.Butil.MediaDevices mediaDevices

var supported = await mediaDevices.IsSupported();
Live sample
support check output
Results will appear here when you interact with the samples.

Enumerate devices

EnumerateDevices

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.

C#
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}");
}
Live sample
enumerate devices output
Results will appear here when you interact with the samples.

Request a stream

GetUserMedia / AttachTo / SetEnabled

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.

C#
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
}
Live sample
stream output
Results will appear here when you interact with the samples.

Fine-grained constraints

GetUserMedia

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.

C#
// 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 } });

Capture a screen, window or tab

GetDisplayMedia / GetDisplaySettings

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.

Razor
@implements IAsyncDisposable
@inject 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>

@code {
    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();
    }
}
Live sample
screen capture output
Results will appear here when you interact with the samples.
Warning:
Always stop what you start A live stream keeps the camera/microphone (and its indicator light) on until every track is stopped. Dispose the MediaStreamHandle when you are done; as a safety net, the MediaDevices service stops any leaked streams when its scope is torn down.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime exposes navigator.mediaDevices. Returns default (false) during prerender/SSR instead of throwing.
EnumerateDevices
ValueTask<MediaDeviceInfo[]> EnumerateDevices()
Lists all input/output media devices. Labels may be empty strings until the user has granted permission to a matching input.
GetUserMedia
ValueTask<MediaStreamHandle?> GetUserMedia(bool audio = true, bool video = false, object? audioConstraints = null, object? videoConstraints = null)
Requests audio and/or video access. Returns a handle when accepted, null when denied or unsatisfiable. Throws ArgumentException when both audio and video are false.
IsDisplayCaptureSupported
ValueTask<bool> IsDisplayCaptureSupported()
True when the runtime exposes navigator.mediaDevices.getDisplayMedia. Returns default (false) during prerender/SSR instead of throwing.
GetDisplayMedia
ValueTask<MediaStreamHandle?> GetDisplayMedia(bool audio = false, DisplayMediaOptions? options = null, object? videoConstraints = null, Action? onEnded = null)
Prompts the user to pick a screen, window or tab. Returns a handle when accepted, null when dismissed or blocked. Requires a user gesture; grants nothing persistent. onEnded fires when the user stops the share from the browser's own bar.
GetDisplaySettings
ValueTask<DisplayMediaSettings?> GetDisplaySettings(MediaStreamHandle stream)
What the capture track actually negotiated: surface kind, size and frame rate. Null when the stream is already gone.
DisplayMediaOptions.FocusBehavior
string? FocusBehavior
Whether the captured tab or window is brought to the front when capture starts: 'focus-captured-surface' (the browser's default) or 'no-focus-change'. Backed by a CaptureController; ignored by runtimes without one, and meaningless for whole-monitor capture.
DisposeAsync
ValueTask DisposeAsync()
On scope/circuit teardown, stops any streams whose handle was never disposed so hardware can't stay live.
MediaStreamHandle.Id
Guid Id { get; }
The internal stream id used to track this stream.
MediaStreamHandle.AttachTo
ValueTask AttachTo(ElementReference videoOrAudioElement)
Attaches this stream to a video or audio element's srcObject.
MediaStreamHandle.SetEnabled
ValueTask SetEnabled(bool enabled)
Pauses or resumes every track without dropping the stream.
MediaStreamHandle.DisposeAsync
ValueTask DisposeAsync()
Stops every track and drops the stream. Idempotent.
An unhandled error has occurred. Reload 🗙