GrVideoPlayer

  • media
  • Медиа
  • API · 10 props

Берут, когда ролик внутри страницы продукта.

import { GrVideoPlayer } from '@feugene/granularity-media/components/GrVideoPlayer'

Обзор

Когда брать

  • ролик внутри страницы продукта — обзор возможностей, где вид плеера обязан совпадать с остальным интерфейсом;
  • запись встречи или урока в личном кабинете: нужна перемотка с клавиатуры и предсказуемая позиция;
  • короткое видео-подтверждение в заявке — снятое тут же и проигранное рядом;
  • тёмная и светлая тема на одной странице: нативные элементы управления тему не знают.

Когда взять другое

НужноБерите
Снять кадр камеройGrCameraCapture
Показать картинку во весь экранGrImageViewer
Плитка вложения в лентеGrFilePreview

Примеры 3 у каждого — живое демо и его код

  • Урок с главами

    Страница управляет плеером его методами: глава вызывает seekTo и play, а события сообщают обратно — timeupdate подсвечивает текущую главу, ended отмечает урок пройденным. Звука в записи нет, поэтому главы — ещё и её текстовая версия. poster показывает, о чём урок, а рамка держит 16:9 до прихода видео.

    Help center · Getting started · Lesson 2 of 5Send your first invoice
    0:00
    Watch to the end to mark the lesson complete · 0:12
  • Запись экрана в обращении

    Слот controls заменяет панель, а не плеер: воспроизведение, клавиши на рамке в фокусе и доступные имена остаются у компонента. Панель добавляет то, что нужно разбору, — комментарии метками на дорожке, каждый — переход к своему моменту, — а seek-step="1" двигает на секунду за стрелку: этого хватает, чтобы найти момент ошибки.

    #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.
  • Ролик вместо GIF

    autoplay вместе с muted — сам браузер запускает только видео без звука — плюс loop и квадратный aspect-ratio. Всё, что движется дольше пяти секунд, должно останавливаться, поэтому слот controls оставляет одну кнопку — паузу; при «уменьшить движение» ролик ждёт на постере, а отказ в запуске приходит событием 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 порождается из пакета · разойтись не может

Props

PropTypeпо умолчаниюОписание
aspectRationumber | undefined16 / 9Соотношение сторон рамки. По умолчанию 16:9 — им снято большинство роликов.
sizeGrVideoPlayerSize | undefinedundefinedСтупень размера: элементы управления и кегль подписей. По умолчанию `md`; берётся из `GrConfigProvider`.
disabledboolean | undefinedfalseПлеер недоступен: кнопки выключены, перемотка вне порядка Tab, запуск не срабатывает.
ariaLabelstring | undefinedundefinedИмя плеера. Не задано — из локали («Video player»).
srcstring | null | undefinednullАдрес ролика.
posterstring | undefinedundefinedКадр до запуска: без него первые секунды видна чёрная рамка.
autoplayboolean | undefinedfalseЗапускать ролик сразу. Браузеры пускают автозапуск только без звука — вместе с `muted`; отказ приходит событием `error`.
loopboolean | undefinedfalseИграть по кругу: после конца ролик начинается заново.
mutedboolean | undefinedfalseНачинать без звука. Для автозапуска обязательно: браузеры блокируют звук.
seekStepnumber | undefined5Шаг перемотки стрелками, секунды.

Слоты

SlotTypeОписание
controls{ playing: boolean; current: number; duration: number; toggle: () => void; }Своя панель управления вместо встроенной.

События

EventСигнатураОписание
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СигнатураОписание
play() => voidЗапустить ролик или поставить на паузу, если он играет; отказ браузера приходит `error`.
seekTo(seconds: number) => voidПеремотать на позицию в секундах, прижатую к границам ролика.
toggleMuted() => voidВключить или выключить звук.
currentnumberТекущая позиция воспроизведения в секундах.
durationnumberДлительность ролика в секундах: `0` до загрузки метаданных, `NaN` у потоковой записи.

Типы

Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.

Импорт
import type { GrVideoPlayerConfigurableProps, GrVideoPlayerProps, GrVideoPlayerEmits } from '@feugene/granularity-media/components/GrVideoPlayer'

Файлdist/types/components/GrVideoPlayer/config.d.ts

grVideoPlayerConfigconst

Своих соседей плеер не рендерит: кнопки нарисованы прямо в нём. GrButton сюда не годится — он рисуется на поверхности приложения, а эти кнопки лежат **на кадре**, где фон заранее неизвестен и меняется каждый кадр. Отсюда собственный класс с белым цветом и полупрозрачной подложкой.

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

Файлdist/types/components/GrVideoPlayer/defaults.d.ts

GrVideoPlayerConfigurablePropsinterface

Настраиваемые через GrConfigProvider пропы. Аугментация реестра идёт по месту объявления — через @feugene/granularity/composables/useGrComponentConfig, а не через реэкспорт: иначе слияние отваливается молча.

interface GrVideoPlayerConfigurableProps {
    size?: GrVideoPlayerSize;
}

Файлdist/types/components/GrVideoPlayer/GrVideoPlayer.vue.d.ts

GrVideoPlayerPropsinterface
interface GrVideoPlayerProps {
    /** Адрес ролика. */
    src?: string | null;
    /** Кадр до запуска: без него первые секунды видна чёрная рамка. */
    poster?: string;
    /** Соотношение сторон рамки. По умолчанию 16:9 — им снято большинство роликов. */
    aspectRatio?: number;
    /**
     * Запускать ролик сразу. Браузеры пускают автозапуск только без звука —
     * вместе с `muted`; отказ приходит событием `error`.
     */
    autoplay?: boolean;
    /** Играть по кругу: после конца ролик начинается заново. */
    loop?: boolean;
    /** Начинать без звука. Для автозапуска обязательно: браузеры блокируют звук. */
    muted?: boolean;
    /** Шаг перемотки стрелками, секунды. */
    seekStep?: number;
    /**
     * Ступень размера: элементы управления и кегль подписей. По умолчанию `md`;
     * берётся из `GrConfigProvider`.
     */
    size?: GrVideoPlayerSize;
    /** Плеер недоступен: кнопки выключены, перемотка вне порядка Tab, запуск не срабатывает. */
    disabled?: boolean;
    /** Имя плеера. Не задано — из локали («Video player»). */
    ariaLabel?: string;
}
GrVideoPlayerEmitsinterface
interface GrVideoPlayerEmits {
    /** Воспроизведение пошло. */
    (e: 'play'): void;
    /** Воспроизведение остановлено. */
    (e: 'pause'): void;
    /** Ролик доигран до конца. С `loop` не приходит. */
    (e: 'ended'): void;
    /** Позиция воспроизведения сменилась — текущая секунда. */
    (e: 'timeupdate', current: number): void;
    /** Ролик не загрузился, запуск отклонён браузером или не открылся полный экран. */
    (e: 'error', error: unknown): void;
}

Файлdist/types/components/GrVideoPlayer/grVideoPlayerStyles.d.ts

GrVideoPlayerSizetype
type GrVideoPlayerSize = 'xs' | 'sm' | 'md' | 'lg';
grVideoPlayerSafelistconst

Safelist пуст: всё, что рисует плеер, лежит целыми литералами здесь и в шаблоне, и granum извлекает это сам — модуль уезжает в общий dist/chunks/, но сборка доходит до него по графу бандла компонента.

const grVideoPlayerSafelist: string[];

Файлdist/types/components/GrVideoPlayer/videoTime.d.ts

formatTimefunction

Время в подписи: 0:07, 4:20, 1:02:03. Часы появляются только когда они есть: 0:00:07 у ролика на семь секунд читается как ошибка. Минуты и секунды всегда двузначные после первой группы — иначе 1:5 выглядит как «час пять».

function formatTime(seconds: number): string;
clampTimefunction

Позиция в пределах ролика: перемотка стрелками легко выходит за края.

function clampTime(value: number, duration: number): number;
progressPercentfunction

Доля просмотренного, 0–100.

function progressPercent(current: number, duration: number): number;
bufferedPercentfunction

Докуда загружено, 0–100. Берётся диапазон, **накрывающий текущую позицию**, а не последний: браузер держит их несколько, и после перемотки назад последний диапазон относится к другому куску ролика — полоса буфера прыгала бы вперёд на пустом месте.

function bufferedPercent(buffered: {
    length: number;
    start: (index: number) => number;
    end: (index: number) => number;

Использование из прозы библиотеки

Полный экран запрашивается у рамки, а не у видео

requestFullscreen на самом <video> отдаёт браузеру его собственный интерфейс: наши кнопки, подписи и клавиатура исчезают ровно там, где нужны больше всего — на весь экран. Поэтому запрос идёт у корневого элемента, и панель едет вместе с кадром.

Полоса буфера берётся вокруг позиции

Браузер держит несколько загруженных диапазонов, и после перемотки назад последний из них относится к другому куску ролика. Полоса, нарисованная по последнему, прыгала бы вперёд на пустом месте, обещая загруженное там, где его нет. Берётся диапазон, накрывающий текущую позицию.

Длительность известна не всегда

У потоковой записи — сделанной MediaRecorder, пришедшей из эфира — длительности нет в заголовке, и браузер отдаёт NaN. Полоса прогресса в этом случае не рисуется вовсе, а подпись показывает одно текущее время: «1:05 / 0:00» обещало бы конец, которого запись не знает.

Время в подписи, а не доля

Дорожка объявляет себя диктору через aria-valuetext — «1:05 / 2:00». Голое aria-valuenow дало бы «65 из 120»: формально верно и бесполезно.

Секунды всегда двузначные (1:05, не 1:5), а часы появляются, только когда они есть: 0:00:07 у семисекундного ролика читается как ошибка.

Звук выключен, если нужен автозапуск

autoplay без muted браузеры блокируют — политика автовоспроизведения не обходится ничем. Компонент этого не скрывает: muted остаётся отдельным пропом, и решение «запускать без звука» принимает приложение, а не библиотека.

Границы
  • нет качества и дорожек. Выбор потока (HLS, DASH), субтитры и звуковые дорожки — работа медиа-движка, а не элементов управления; их подключают сторонним плеером;
  • нет плейлиста. Компонент показывает один ролик; очередь и переходы между роликами остаются приложению;
  • нет скорости воспроизведения. Она нужна редко и стоит ещё одного меню на панели, где место дороже.