Skip to main content

MediaPlayer

import { MediaPlayer } from '@react-x11/components/media-player';

<MediaPlayer
src={file}
aspectRatio="16:9"
volume={0.8}
style={{ flexGrow: 1 }}
onProgress={({ position, duration }) => setScrub(position / duration)}
onEnded={next}
/>;

mpv or VLC, embedded in a react-x11 window, with real transport control rather than a respawn per action. The same component as <Terminal> with different argv — both are built on embed and core's <foreign>.

backend defaults to 'auto': mpv if it is installed, else VLC. With neither, fallback renders and onError gets a BackendUnavailableError naming what was looked for. That is an ordinary state of a healthy machine, not an exception.

Props

Source and playback

PropTypeNotes
srcstringA path or a URL. Changing it does not restart the player — the new source is loaded into the running one, so the window never blinks.
backendMediaBackendName'auto' (default), 'mpv', 'vlc'.
autoPlaybooleanDefault true.
pausedbooleanDrive it if you want to. Leave it out and the player is uncontrolled — autoPlay decides how it starts and the handle moves it from there.
volumenumber0–1. Default: the player's own.
mutedboolean
loopboolean
aspectRatiostring'16:9', '4:3'. The player letterboxes inside the element's rect.
osdbooleanThe player's own on-screen controls. Off by default: they are drawn by the player and will not match the app's theme.
extraArgsreadonly string[]Appended to the player's command line verbatim.

Process and layout

PropTypeNotes
enabledbooleanFalse holds off spawning entirely.
stopSignalstringSent on unmount and restart. Default SIGTERM.
processesProcessHostWhere the process runs. See embed.
focusablebooleanA video surface is not a control; default false, unlike <Terminal>.
fallbackReactNodeRendered instead of the surface when no player is installed.
styleStyle | Style[]

Events

PropTypeNotes
onProgress(p: MediaProgress) => voidPosition and duration as the player reports them. mpv only.
onEnded(info) => voidThe file reached its end. Not fired for stop() or a new src.
onPlayingChange(playing: boolean) => void
onExit(info: ExitInfo) => voidThe player process ended.
onError(err: Error) => voidSpawn failures, control-channel failures, and BackendUnavailableError.

MediaPlayerHandle

player.current.seek(90);
if (player.current.reportsProgress) showScrubber();

play(), pause(), seek(seconds), setVolume(0–1), stop() (playback stops, the window stays, idle), restart(), signal(sig?), plus the read-only pid, windowId, backend, status and reportsProgress.

Live commands, and the VLC asymmetry

src, volume, muted and paused are live commands, sent over mpv's JSON IPC socket — changing them does not respawn the player.

Under VLC that channel is write-only: play/pause/seek/volume work and onProgress never fires. handle.reportsProgress says which you have, so a scrubber can be hidden rather than sit at zero.

The player's window stacks above everything you draw

Same rule <glarea> has, and the same one <Terminal>'s embedded backends have. A transport bar cannot be a <box> over the video — put it beside the element, or in a sibling <popup>. There is no vt-style native backend here: decoding video is the player's job.

Example

npm run examples:media-player -- <file>

Needs a real $DISPLAY and mpv or VLC installed.