GrVideoPlayer
Берут, когда ролик внутри страницы продукта.
Обзор
Когда брать
- ролик внутри страницы продукта — обзор возможностей, где вид плеера обязан совпадать с остальным интерфейсом;
- запись встречи или урока в личном кабинете: нужна перемотка с клавиатуры и предсказуемая позиция;
- короткое видео-подтверждение в заявке — снятое тут же и проигранное рядом;
- тёмная и светлая тема на одной странице: нативные элементы управления тему не знают.
Когда взять другое
| Нужно | Берите |
|---|---|
| Снять кадр камерой | GrCameraCapture |
| Показать картинку во весь экран | GrImageViewer |
| Плитка вложения в ленте | GrFilePreview |
Примеры 3 у каждого — живое демо и его код
Урок с главами
Страница управляет плеером его методами: глава вызывает
seekToиplay, а события сообщают обратно —timeupdateподсвечивает текущую главу,endedотмечает урок пройденным. Звука в записи нет, поэтому главы — ещё и её текстовая версия.posterпоказывает, о чём урок, а рамка держит 16:9 до прихода видео.Help center · Getting started · Lesson 2 of 5Send your first invoice 0:00Watch to the end to mark the lesson complete · 0:12Запись экрана в обращении
Слот
controlsзаменяет панель, а не плеер: воспроизведение, клавиши на рамке в фокусе и доступные имена остаются у компонента. Панель добавляет то, что нужно разбору, — комментарии метками на дорожке, каждый — переход к своему моменту, — аseek-step="1"двигает на секунду за стрелку: этого хватает, чтобы найти момент ошибки.#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.
Ролик вместо 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
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
aspectRatio | number | undefined | 16 / 9 | Соотношение сторон рамки. По умолчанию 16:9 — им снято большинство роликов. |
size | GrVideoPlayerSize | undefined | undefined | Ступень размера: элементы управления и кегль подписей. По умолчанию `md`; берётся из `GrConfigProvider`. |
disabled | boolean | undefined | false | Плеер недоступен: кнопки выключены, перемотка вне порядка Tab, запуск не срабатывает. |
ariaLabel | string | undefined | undefined | Имя плеера. Не задано — из локали («Video player»). |
src | string | null | undefined | null | Адрес ролика. |
poster | string | undefined | undefined | Кадр до запуска: без него первые секунды видна чёрная рамка. |
autoplay | boolean | undefined | false | Запускать ролик сразу. Браузеры пускают автозапуск только без звука — вместе с `muted`; отказ приходит событием `error`. |
loop | boolean | undefined | false | Играть по кругу: после конца ролик начинается заново. |
muted | boolean | undefined | false | Начинать без звука. Для автозапуска обязательно: браузеры блокируют звук. |
seekStep | number | undefined | 5 | Шаг перемотки стрелками, секунды. |
Слоты
| Slot | Type | Описание |
|---|---|---|
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 | Включить или выключить звук. |
current | number | Текущая позиция воспроизведения в секундах. |
duration | number | Длительность ролика в секундах: `0` до загрузки метаданных, `NaN` у потоковой записи. |
Типы
Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.
import type { GrVideoPlayerConfigurableProps, GrVideoPlayerProps, GrVideoPlayerEmits } from '@feugene/granularity-media/components/GrVideoPlayer'Файлdist/types/components/GrVideoPlayer/config.d.ts
Своих соседей плеер не рендерит: кнопки нарисованы прямо в нём.
GrButton сюда не годится — он рисуется на поверхности приложения, а эти
кнопки лежат **на кадре**, где фон заранее неизвестен и меняется каждый
кадр. Отсюда собственный класс с белым цветом и полупрозрачной подложкой.
const grVideoPlayerConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrVideoPlayer">;Файлdist/types/components/GrVideoPlayer/defaults.d.ts
Настраиваемые через GrConfigProvider пропы.
Аугментация реестра идёт по месту объявления — через
@feugene/granularity/composables/useGrComponentConfig, а не через реэкспорт:
иначе слияние отваливается молча.
interface GrVideoPlayerConfigurableProps {
size?: GrVideoPlayerSize;
}Файлdist/types/components/GrVideoPlayer/GrVideoPlayer.vue.d.ts
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;
}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
type GrVideoPlayerSize = 'xs' | 'sm' | 'md' | 'lg';Safelist пуст: всё, что рисует плеер, лежит целыми литералами здесь и в
шаблоне, и granum извлекает это сам — модуль уезжает в общий dist/chunks/,
но сборка доходит до него по графу бандла компонента.
const grVideoPlayerSafelist: string[];Файлdist/types/components/GrVideoPlayer/videoTime.d.ts
Время в подписи: 0:07, 4:20, 1:02:03.
Часы появляются только когда они есть: 0:00:07 у ролика на семь секунд
читается как ошибка. Минуты и секунды всегда двузначные после первой группы —
иначе 1:5 выглядит как «час пять».
function formatTime(seconds: number): string;Позиция в пределах ролика: перемотка стрелками легко выходит за края.
function clampTime(value: number, duration: number): number;Доля просмотренного, 0–100.
function progressPercent(current: number, duration: number): number;Докуда загружено, 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), субтитры и звуковые дорожки — работа медиа-движка, а не элементов управления; их подключают сторонним плеером;
- нет плейлиста. Компонент показывает один ролик; очередь и переходы между роликами остаются приложению;
- нет скорости воспроизведения. Она нужна редко и стоит ещё одного меню на панели, где место дороже.
Пропы этого примера