loading
Note:
A recorder consumes a stream someone else opened Get a MediaStreamHandle from MediaDevices first - GetUserMedia for a camera or microphone, GetDisplayMedia for a screen share. Stopping the recording does not stop that stream, and disposing the stream does not save the recording: they are two separate lifetimes.

Support and container check

IsSupported / IsTypeSupported / GetSupportedTypes

Which containers and codecs exist differs per engine - Chromium and Firefox speak WebM, Safari only speaks MP4 - so probe rather than hard-coding a MIME type. GetSupportedTypes filters a candidate list down to what this browser can actually record, keeping your order, so the first entry is your best available choice.

C#
@inject Bit.Butil.MediaRecorder recorder

var supported = await recorder.IsSupported();
var webm = await recorder.IsTypeSupported("video/webm;codecs=vp9,opus");

// MediaRecorder.CommonVideoTypes / CommonAudioTypes are ready-made candidate lists:
var available = await recorder.GetSupportedTypes(MediaRecorder.CommonVideoTypes);
var best = available.FirstOrDefault();
Live sample
support check output
Results will appear here when you interact with the samples.

Record and play back

Start / Stop / StopAndCreateObjectUrl

Start returns a handle, or null when the container isn't supported or the stream is already stopped. Stop is what produces the recording - disposing without stopping throws the take away. StopAndCreateObjectUrl keeps the bytes inside the browser and hands back a blob: URL a media element can play directly, which is much cheaper than pulling a whole video across the interop boundary.

C#
private MediaStreamHandle? _stream;
private MediaRecordingHandle? _recording;
private string? _playbackUrl;

// 1. open a stream, 2. record it
_stream = await mediaDevices.GetUserMedia(audio: true, video: true);
_recording = await recorder.Start(_stream!, new MediaRecorderOptions
{
    MimeType = (await recorder.GetSupportedTypes()).FirstOrDefault(),
});

// later - the blob stays in the browser, only the URL crosses:
var media = await _recording!.StopAndCreateObjectUrl();
_playbackUrl = media?.ObjectUrl;   // <video src="@_playbackUrl" controls />

// or pull the bytes into C# to upload or save:
// var media = await _recording.Stop();
// await File.WriteAllBytesAsync(path, media!.Data!);

// release the blob when you're done with it (the service, not the handle - stopping
// the recording already retired the handle):
await recorder.RevokeObjectUrl(_playbackUrl);
Live sample
State No recording yet.
recording output
Results will appear here when you interact with the samples.

Stream the slices as they arrive

Start(onData, timesliceMs) / RequestData

Pass a timeslice and an onData callback and the browser hands you each encoded slice as it is produced, so a long recording can be uploaded while it runs instead of being held whole in memory. RequestData flushes what has been captured so far without ending the take.

Razor
@inject Bit.Butil.MediaRecorder recorder

@code {
    private long _bytesSoFar;
    private MediaStreamHandle? _stream;        // from mediaDevices.GetUserMedia
    private MediaRecordingHandle? _recording;

    private async Task Start()
    {
        _recording = await recorder.Start(
            _stream!,
            onData: chunk => InvokeAsync(() =>
            {
                _bytesSoFar += chunk.Length;   // upload it, append it to a file, ...
                StateHasChanged();
            }),
            timesliceMs: 1000);   // one slice per second
    }

    // flush early, without stopping:
    private async Task Flush() => await _recording!.RequestData();
}
Live sample
Slices received 0 slice(s), 0 B total
chunk output
Results will appear here when you interact with the samples.
Warning:
Stop the stream too Stopping a recording leaves the camera or microphone running - the recorder never owned it. Dispose the MediaStreamHandle when the user is finished, or the indicator light stays on.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime exposes MediaRecorder. Returns default (false) during prerender/SSR instead of throwing.
IsTypeSupported
ValueTask<bool> IsTypeSupported(string mimeType)
True when this engine can record the given container/codec string.
GetSupportedTypes
ValueTask<string[]> GetSupportedTypes(string[]? candidates = null)
Filters candidates down to the recordable ones, preserving your order. Defaults to CommonVideoTypes.
CommonVideoTypes
static readonly string[] CommonVideoTypes
Video containers worth probing on today's engines, most-preferred first.
CommonAudioTypes
static readonly string[] CommonAudioTypes
Audio-only containers worth probing, most-preferred first.
Start
ValueTask<MediaRecordingHandle?> Start(MediaStreamHandle stream, MediaRecorderOptions? options = null, Action<byte[]>? onData = null, int? timesliceMs = null, Action<string>? onError = null)
Starts recording a stream. Null when MediaRecorder is missing, the stream is stopped, or the container isn't supported.
RevokeObjectUrl
ValueTask RevokeObjectUrl(string? objectUrl)
Releases an object URL after its handle is gone - the normal case, since stopping a recording retires its handle. Safe with null.
DisposeAsync
ValueTask DisposeAsync()
On scope/circuit teardown, cancels any recording whose handle was never disposed.
MediaRecordingHandle.GetState
ValueTask<string> GetState()
"recording", "paused", or "inactive" once the take is over.
MediaRecordingHandle.GetMimeType
ValueTask<string> GetMimeType()
The container the browser settled on, which can differ from the one requested.
MediaRecordingHandle.Pause
ValueTask Pause()
Pauses encoding without ending the take. No-op unless recording.
MediaRecordingHandle.Resume
ValueTask Resume()
Resumes a paused take. No-op unless paused.
MediaRecordingHandle.RequestData
ValueTask RequestData()
Emits everything captured so far to the onData callback and keeps recording.
MediaRecordingHandle.Stop
ValueTask<RecordedMedia?> Stop()
Stops and returns the encoded bytes. Null when already stopped or disposed.
MediaRecordingHandle.StopAndCreateObjectUrl
ValueTask<RecordedMedia?> StopAndCreateObjectUrl()
Stops and returns a blob: URL, leaving the bytes in the browser. Release it with RevokeObjectUrl.
MediaRecordingHandle.RevokeObjectUrl
ValueTask RevokeObjectUrl(string? objectUrl)
Releases an object URL. Safe to call more than once, and with null.
MediaRecordingHandle.DisposeAsync
ValueTask DisposeAsync()
Abandons the recording, discarding anything captured. No-op once stopped.
An unhandled error has occurred. Reload 🗙