Camera & microphone
Capture the camera, microphone, or screen and show it in a <video> via IMediaDevices (getUserMedia / getDisplayMedia) — for photo capture, video calls, or screen recording. WASM-only: capture needs a live user gesture and a secure context. Dispose the stream handle to stop every track and release the hardware (the camera indicator turns off).
GetUserMediaAsync(constraints) / GetDisplayMediaAsync() return a disposable IMediaStreamHandle; AttachToAsync(ElementRef) wires the stream to a <video> and plays it. The live MediaStream stays JS-side under a minted id. Gate on IsSupportedAsync and try/catch — a denied request throws.
MediaDevicesDemo.cs
using Rask.Core;
using Rask.Site;
using Rask.Wasm.Browser;
namespace Rask.Site.Features;
/// <summary>
/// <see cref="IMediaDevices" /> — capture the camera/microphone (or screen) and show it in a
/// <c><video></c>. The live stream lives JS-side; dispose the handle to stop every track and
/// release the hardware (the camera indicator turns off).
/// </summary>
public sealed partial class MediaDevicesDemo(IMediaDevices media) : Component, IAsyncDisposable
{
private readonly ElementRef _video = ElementRef.New();
private IMediaStreamHandle? _stream;
private string _status = "(idle)";
protected override Component? Render() =>
Div.Class($"{Tw.Card} shadow-sm border-0")[
Div.Class(Tw.CardBody)[
Video
.Ref(_video)
.Width(320)
.Height(240)
.Muted(true)
.PlaysInline(true)
.Class("rounded border mb-2 bg-slate-900 block"),
Div.Class("flex gap-2 flex-wrap mb-2")[
Button.Class(Tw.BtnPrimary).Id("media-start").OnClickAsync(StartCamera)[
UiIcon.Name(UiIconName.VideoCamera).Class("me-1"), "Start camera"],
Button.Class(Tw.BtnOutlinePrimary).Id("media-screen").OnClickAsync(ShareScreen)[
UiIcon.Name(UiIconName.Desktop).Class("me-1"), "Share screen"],
Button
.Class(Tw.BtnOutlineDanger)
.Id("media-stop")
.Disabled(_stream is null)
.OnClickAsync(Stop)["Stop"]
],
Div.Class("text-sm text-ui-muted")["Status: ", Code.Id("media-status")[_status]]
]
];
private Task StartCamera() => Capture(() => media.GetUserMediaAsync(new MediaConstraints(Video: true)), "Camera live");
private Task ShareScreen() => Capture(() => media.GetDisplayMediaAsync(), "Screen sharing");
private async Task Capture(Func<ValueTask<IMediaStreamHandle>> request, string okStatus)
{
try
{
if (!await media.IsSupportedAsync())
{
_status = "Media capture not supported in this browser";
return;
}
await StopInternal();
_stream = await request();
await _stream.AttachToAsync(_video);
_status = okStatus;
}
catch (Exception ex)
{
_status = "Failed: " + ex.Message;
}
}
private async Task Stop()
{
await StopInternal();
_status = "Stopped — hardware released";
}
private async Task StopInternal()
{
if (_stream is not null)
{
await _stream.DisposeAsync();
_stream = null;
}
}
public async ValueTask DisposeAsync() => await StopInternal();
}
Live result
Status:
(idle)