GrVideoPlayer
Overview
When to take it
- a clip inside a product page — a feature overview, where the look of the player is obliged to match the rest of the interface;
- a recording of a meeting or a lesson in a personal account: seeking from the keyboard and a predictable position are needed;
- a short video confirmation in an application — shot right there and played next to it;
- a dark and a light theme on one page: the native controls do not know about the theme.
When to take something else
| Need | Take |
|---|---|
| Take a shot with the camera | GrCameraCapture |
| Show a picture full screen | GrImageViewer |
| A tile of an attachment in a feed | GrFilePreview |
Examples 3 each with a live demo and its code
A lesson with chapters
The page drives the player through its methods: a chapter calls
seekToandplay, and the events report back —timeupdatelights the current chapter,endedmarks the lesson done. The recording has no sound, so the chapters are its text version too.postershows what the lesson is about, and the frame keeps 16:9 before the video arrives.Help center · Getting started · Lesson 2 of 5Send your first invoice 0:00Watch to the end to mark the lesson complete · 0:12A recording in a support ticket
The
controlsslot replaces the panel, not the player: playback, the keys on the focused frame and the accessible names stay with the component. The panel adds what a review needs — comments as markers on the track, each a jump to its moment — andseek-step="1"moves a second per arrow, enough to find the moment of a bug.#4821 · «Send invoice» does nothingJonas Weber, Fabrikam Interiors · screen recording attachedHighOpen0:00 / 0:08Comments on the recording- JWJonas Weber · Here I click «Send invoice». A spinner, then nothing.
- JWJonas Weber · Clicked again — same thing.
- MOMina Ortiz · The error appears only on the second try: the customer has no billing email. The check should run before sending.
A clip in place of a GIF
autoplaywithmuted— browsers let only silent video start by itself — plusloopand a squareaspect-ratio. Anything that moves for more than five seconds needs a way to stop it, so thecontrolsslot keeps one button, pause; with «reduce motion» on, the clip waits on its poster, and a refused start comes aserror.NewWhat’s new · 13 October 2026Reminders for several invoices at onceTick the overdue invoices and send one reminder each in a single step. Each customer gets their own email with their own invoice — nothing is merged.
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
aspectRatio | number | undefined | 16 / 9 | Aspect ratio of the frame. 16:9 by default — most clips are shot in it. |
size | GrVideoPlayerSize | undefined | undefined | The size step: the controls and the type size of the captions. `md` by default; taken from `GrConfigProvider`. |
disabled | boolean | undefined | false | The player is unavailable: the buttons are disabled, the seek bar is out of the Tab order, playback does not start. |
ariaLabel | string | undefined | undefined | The name of the player. Not set — from the locale ("Video player"). |
src | string | null | undefined | null | The URL of the clip. |
poster | string | undefined | undefined | A frame shown before playback: without it a black frame is visible for the first seconds. |
autoplay | boolean | undefined | false | Start the clip right away. Browsers allow autoplay only without sound — together with `muted`; a refusal arrives as the `error` event. |
loop | boolean | undefined | false | Play in a loop: after the end the clip starts over. |
muted | boolean | undefined | false | Start without sound. Mandatory for autoplay: browsers block sound. |
seekStep | number | undefined | 5 | Arrow-key seek step, seconds. |
Slots
| Slot | Type | Description |
|---|---|---|
controls | { playing: boolean; current: number; duration: number; toggle: () => void; } | A custom control bar instead of the built-in one. |
Events
| Event | Signature | Description |
|---|---|---|
error | (event: "error", error: unknown): void | — |
play | (event: "play"): void | — |
pause | (event: "pause"): void | — |
ended | (event: "ended"): void | — |
timeupdate | (event: "timeupdate", current: number): void | — |
Methods
| Methods | Signature | Description |
|---|---|---|
play | () => void | Start the clip, or pause it if it is playing; a refusal by the browser arrives as `error`. |
seekTo | (seconds: number) => void | Seek to a position in seconds, clamped to the bounds of the clip. |
toggleMuted | () => void | Turn the sound on or off. |
current | number | The current playback position in seconds. |
duration | number | The duration of the clip in seconds: `0` before the metadata loads, `NaN` for a live stream. |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrVideoPlayerConfigurableProps, GrVideoPlayerProps, GrVideoPlayerEmits } from '@feugene/granularity-media/components/GrVideoPlayer'Filedist/types/components/GrVideoPlayer/config.d.ts
const grVideoPlayerConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrVideoPlayer">;Filedist/types/components/GrVideoPlayer/defaults.d.ts
interface GrVideoPlayerConfigurableProps {
size?: GrVideoPlayerSize;
}Filedist/types/components/GrVideoPlayer/GrVideoPlayer.vue.d.ts
interface GrVideoPlayerProps {
src?: string | null;
poster?: string;
aspectRatio?: number;
autoplay?: boolean;
loop?: boolean;
muted?: boolean;
seekStep?: number;
size?: GrVideoPlayerSize;
disabled?: boolean;
ariaLabel?: string;
}interface GrVideoPlayerEmits {
(e: 'play'): void;
(e: 'pause'): void;
(e: 'ended'): void;
(e: 'timeupdate', current: number): void;
(e: 'error', error: unknown): void;
}Filedist/types/components/GrVideoPlayer/grVideoPlayerStyles.d.ts
type GrVideoPlayerSize = 'xs' | 'sm' | 'md' | 'lg';const grVideoPlayerSafelist: string[];Filedist/types/components/GrVideoPlayer/videoTime.d.ts
function formatTime(seconds: number): string;function clampTime(value: number, duration: number): number;function progressPercent(current: number, duration: number): number;function bufferedPercent(buffered: {
length: number;
start: (index: number) => number;
end: (index: number) => number;Usage from the library prose
Full screen is requested for the frame rather than for the video
A requestFullscreen on the <video> itself gives the browser its own interface: our buttons,
labels and keyboard disappear exactly where they are needed most — on the full screen. The request
therefore goes for the root element, and the panel travels together with the frame.
The buffer bar is taken around the position
The browser holds several loaded ranges, and after seeking backwards the last of them belongs to another piece of the clip. A bar drawn by the last one would jump forward out of nowhere, promising loaded content where there is none. The range covering the current position is taken.
The duration is not always known
A streamed recording — made with MediaRecorder, arrived from a broadcast — has no duration in its
header, and the browser gives away NaN. The progress bar is not drawn at all in that case, and the
label shows the current time alone: “1:05 / 0:00” would promise an end the recording does not know.
The sound is off if autoplay is needed
autoplay without muted is blocked by browsers — the autoplay policy is not bypassed by anything.
The component does not hide that: muted remains a separate prop, and the decision to “start without
sound” is made by the application rather than by the library.
Limits
- there is no quality and no tracks. Choosing a stream (HLS, DASH), subtitles and audio tracks are the work of a media engine rather than of controls; they are connected with a third-party player;
- there is no playlist. The component shows one clip; the queue and the transitions between clips are left to the application;
- there is no playback speed. It is needed rarely and costs one more menu on a panel where room is more expensive.
Props in this example