GrVideoPlayer

Package: @feugene/granularity-mediacompanionGroup: misc

Machine-translated from the Russian original, not yet reviewed. Read the original

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

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.

Install

npm i @feugene/granularity-media

Import

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

API

The API for this component has not been generated yet: the showcase generator only covers the core so far. Until it does, the reference lives in the package documentation.

Examples 1

Basic

Basic
<script setup lang="ts">
import { ref } from 'vue'

import { GrBadge } from '@feugene/granularity'

/**
 * Ролик лежит в `public/demo` и собирается скриптом
 * `scripts/generate-demo-video.mjs`: витрина обязана работать без сети, а
 * тащить бинарь из внешнего источника — значит зависеть от чужого хостинга.
 */
const source = `${import.meta.env.BASE_URL}demo/sample.webm`

const state = ref<'ready' | 'playing' | 'paused' | 'ended'>('ready')
const position = ref(0)

function onTime(current: number) {
  position.value = current
}
</script>

<template>
  <div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(0,300px)]">
    <GrVideoPlayer
      :src="source"
      :aspect-ratio="16 / 9"
      muted
      @play="state = 'playing'"
      @pause="state = 'paused'"
      @ended="state = 'ended'"
      @timeupdate="onTime"
    />

    <div class="showcase-demo-panel grid content-start gap-3 rounded-[var(--gr-radius-lg)] border p-4">
      <p class="showcase-demo-text text-sm">
        Состояние: <GrBadge size="sm" tone="neutral">{{ state }}</GrBadge>
      </p>
      <p class="showcase-demo-text text-sm">
        Позиция: <strong>{{ position.toFixed(1) }} с</strong>
      </p>

      <p class="showcase-demo-text text-sm">
        Элементы управления свои, а не браузерные: нативные выглядят по-разному в каждом
        браузере и не знают ни про темы, ни про размеры дизайн-системы.
      </p>

      <p class="showcase-demo-text text-sm">
        Клавиатура работает, когда плеер в фокусе: пробел — пуск и пауза, стрелки влево и
        вправо — перемотка на пять секунд, <code>Home</code> и <code>End</code> — к началу и
        концу, <code>M</code> — звук, <code>F</code> — во весь экран.
      </p>

      <p class="showcase-demo-text text-sm">
        Длительность плеер берёт у браузера и не выдумывает: у потоковой записи её в заголовке
        нет, и тогда вместо «1:05 / 0:00» показывается одно текущее время, а полоса не рисуется
        вовсе — обещать конец, которого запись не знает, нельзя.
      </p>
    </div>
  </div>
</template>

Component documentationAll components