loading

Support

IsSupported

True when media elements expose a remote object. Chromium and Safari implement it; note that the presence of the API says nothing about whether any device is on the network - that is what WatchAvailability answers.

C#
@inject Bit.Butil.RemotePlayback remotePlayback

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

Is there anything to cast to?

WatchAvailability

The callback fires immediately with the current answer and again whenever it changes, so a cast button can be hidden until there is somewhere to cast to. Watching costs battery - the browser keeps scanning the network - so dispose the subscription when the UI it feeds goes away.

C#
var subscription = await remotePlayback.WatchAvailability(mediaElement, available =>
{
    showCastButton = available;
    StateHasChanged();
});

// later - or when the component is disposed
await subscription!.DisposeAsync();
Live sample
availability output
Results will appear here when you interact with the samples.

Casting, and watching what happens next

Prompt / GetState / SubscribeStateChange

Prompt opens the browser's own device picker and needs a user gesture. A true from it means the picker was answered, not that playback has started - the state subscription is what reports the connection being made, and the disconnect that happens when the user stops the cast from the TV.

C#
await remotePlayback.SubscribeStateChange(mediaElement, state =>
{
    // Connecting -> Connected, and Disconnected when the user ends it from the device
    Console.WriteLine(state);
});

// from a click handler:
var prompted = await remotePlayback.Prompt(mediaElement);

var state = await remotePlayback.GetState(mediaElement);
Live sample
state output
Results will appear here when you interact with the samples.

Opting an element out

SetDisabled

Sets the element's disableRemotePlayback property, which removes the cast affordance the browser draws inside its own controls. Worth doing for media that must not leave the device - a DRM stream whose licence forbids it, or a local preview.

C#
await remotePlayback.SetDisabled(mediaElement, true);   // no cast button on this element
await remotePlayback.SetDisabled(mediaElement, false);
Live sample
opt-out output
Results will appear here when you interact with the samples.
Note:
One element, not the page Remote playback hands over a single media element: the remote device fetches and plays the media itself, and the element in the page becomes a remote control that decodes nothing. To put arbitrary page content on a second screen instead, use Presentation.
Warning:
The picker needs a gesture, and the user owns the answer Prompt only works from inside a click or key handler, and it returns false for every outcome the app cannot control - dismissed, nothing found, blocked by the embedder. Treat the state subscription as the source of truth rather than the return value.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when media elements expose a remote object.
GetState
ValueTask<RemotePlaybackState> GetState(ElementReference mediaElement)
Disconnected, Connecting or Connected.
SetDisabled
ValueTask<bool> SetDisabled(ElementReference mediaElement, bool disabled)
Opts the element out of remote playback, removing the browser's cast button.
Prompt
ValueTask<bool> Prompt(ElementReference mediaElement)
Opens the browser's device picker. Needs a user gesture.
WatchAvailability
ValueTask<ButilSubscription?> WatchAvailability(ElementReference mediaElement, Action<bool> handler)
Whether there is any device to cast to, now and on every change.
SubscribeStateChange
ValueTask<ButilSubscription> SubscribeStateChange(ElementReference mediaElement, Action<RemotePlaybackState> handler)
Watches the element connecting to and disconnecting from a device.
DisposeAsync
ValueTask DisposeAsync()
Cancels every watch and detaches every listener on teardown; media already playing remotely is left alone.
An unhandled error has occurred. Reload 🗙