GrVideoPlayer

  • media
  • media
  • API · 10 props
import { GrVideoPlayer } from '@feugene/granularity-media/components/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

NeedTake
Take a shot with the cameraGrCameraCapture
Show a picture full screenGrImageViewer
A tile of an attachment in a feedGrFilePreview

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 seekTo and play, and the events report back — timeupdate lights the current chapter, ended marks the lesson done. The recording has no sound, so the chapters are its text version too. poster shows 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:00
    Watch to the end to mark the lesson complete · 0:12
  • A recording in a support ticket

    The controls slot 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 — and seek-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 attachedHighOpen
    0:00 / 0:08
    Comments on the recording
    1. JWJonas Weber · Here I click «Send invoice». A spinner, then nothing.
    2. JWJonas Weber · Clicked again — same thing.
    3. 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

    autoplay with muted — browsers let only silent video start by itself — plus loop and a square aspect-ratio. Anything that moves for more than five seconds needs a way to stop it, so the controls slot keeps one button, pause; with «reduce motion» on, the clip waits on its poster, and a refused start comes as error.

    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

PropTypedefaultDescription
aspectRationumber | undefined16 / 9Aspect ratio of the frame. 16:9 by default — most clips are shot in it.
sizeGrVideoPlayerSize | undefinedundefinedThe size step: the controls and the type size of the captions. `md` by default; taken from `GrConfigProvider`.
disabledboolean | undefinedfalseThe player is unavailable: the buttons are disabled, the seek bar is out of the Tab order, playback does not start.
ariaLabelstring | undefinedundefinedThe name of the player. Not set — from the locale ("Video player").
srcstring | null | undefinednullThe URL of the clip.
posterstring | undefinedundefinedA frame shown before playback: without it a black frame is visible for the first seconds.
autoplayboolean | undefinedfalseStart the clip right away. Browsers allow autoplay only without sound — together with `muted`; a refusal arrives as the `error` event.
loopboolean | undefinedfalsePlay in a loop: after the end the clip starts over.
mutedboolean | undefinedfalseStart without sound. Mandatory for autoplay: browsers block sound.
seekStepnumber | undefined5Arrow-key seek step, seconds.

Slots

SlotTypeDescription
controls{ playing: boolean; current: number; duration: number; toggle: () => void; }A custom control bar instead of the built-in one.

Events

EventSignatureDescription
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

MethodsSignatureDescription
play() => voidStart the clip, or pause it if it is playing; a refusal by the browser arrives as `error`.
seekTo(seconds: number) => voidSeek to a position in seconds, clamped to the bounds of the clip.
toggleMuted() => voidTurn the sound on or off.
currentnumberThe current playback position in seconds.
durationnumberThe 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
import type { GrVideoPlayerConfigurableProps, GrVideoPlayerProps, GrVideoPlayerEmits } from '@feugene/granularity-media/components/GrVideoPlayer'

Filedist/types/components/GrVideoPlayer/config.d.ts

grVideoPlayerConfigconst
const grVideoPlayerConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrVideoPlayer">;

Filedist/types/components/GrVideoPlayer/defaults.d.ts

GrVideoPlayerConfigurablePropsinterface
interface GrVideoPlayerConfigurableProps {
    size?: GrVideoPlayerSize;
}

Filedist/types/components/GrVideoPlayer/GrVideoPlayer.vue.d.ts

GrVideoPlayerPropsinterface
interface GrVideoPlayerProps {
    src?: string | null;
    poster?: string;
    aspectRatio?: number;
    autoplay?: boolean;
    loop?: boolean;
    muted?: boolean;
    seekStep?: number;
    size?: GrVideoPlayerSize;
    disabled?: boolean;
    ariaLabel?: string;
}
GrVideoPlayerEmitsinterface
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

GrVideoPlayerSizetype
type GrVideoPlayerSize = 'xs' | 'sm' | 'md' | 'lg';
grVideoPlayerSafelistconst
const grVideoPlayerSafelist: string[];

Filedist/types/components/GrVideoPlayer/videoTime.d.ts

formatTimefunction
function formatTime(seconds: number): string;
clampTimefunction
function clampTime(value: number, duration: number): number;
progressPercentfunction
function progressPercent(current: number, duration: number): number;
bufferedPercentfunction
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 time in the label rather than a share

The track announces itself to a screen reader through aria-valuetext — “1:05 / 2:00”. A bare aria-valuenow would give “65 of 120”: formally correct and useless.

The seconds are always two digits (1:05, not 1:5), and the hours appear only when there are any: 0:00:07 on a seven-second clip reads as an error.

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.