Использование requestvideoframecallback в javascript для точной синхронизации видео

Зачем вообще нужен requestVideoFrameCallback

Если вы хоть раз пытались делать «умную» работу с видео в браузере на JavaScript, вы наверняка упирались в одну и ту же проблему: точной привязки к кадрам нет, таймеры врут, `timeupdate` дергается слишком редко, а `setInterval` живёт своей жизнью.

Вот тут и выезжает на сцену новый герой HTML5‑видео — метод `requestVideoFrameCallback`.

Это специальный API для `

Где это работает

На момент написания, `requestVideoFrameCallback` поддерживается в современных версиях Chromium‑браузеров (Chrome, Edge, Opera, Brave) и Safari. Firefox пока отстаёт, поэтому для продакшена стоит предусмотреть запасной вариант.

Простейшая проверка:

```js
const video = document.querySelector('video');

if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
console.log('Поддержка есть!');
} else {
console.log('Нужно использовать полифилл или таймеры.');
}
```

Базовая идея: как работает requestVideoFrameCallback

Метод вешается на конкретный `

```js
const video = document.querySelector('video');

let handle;

function onVideoFrame(now, metadata) {
// ваша логика тут

handle = video.requestVideoFrameCallback(onVideoFrame);
}

handle = video.requestVideoFrameCallback(onVideoFrame);
```

Он принимает колбэк с двумя аргументами:

- `now` — высокоточная отметка времени (аналогично `performance.now()`),
- `metadata` — объект с кучей полезной информации: `mediaTime`, `presentedFrames`, `expectedDisplayTime` и т.д.

Главное отличие от обычной `javascript работа с видео html5` в том, что вы получаете событие на каждый кадр, а не раз в сколько-то миллисекунд как повезёт.

Чем это лучше setInterval и timeupdate

Коротко:

- `timeupdate` вызывается примерно 4–5 раз в секунду — для точных эффектов бесполезно.
- `setInterval` и `requestAnimationFrame` живут по своему расписанию и не знают, когда браузер реально показал кадр.
- `requestVideoFrameCallback` сообщает точную, синхронизированную с рендером кадра информацию.

Результат:
обработка видео в браузере JavaScript становится предсказуемой и аккуратной, без «прыжков» и рассинхрона.

Практика: типичный паттерн использования

Шаг 1. Запускаем колбэк только когда видео играет

Не стоит вызывать колбэк, когда видео на паузе — это лишняя нагрузка. Привяжем старт и остановку к событиям плеера:

```js
const video = document.querySelector('video');
let frameHandle = null;

function onFrame(now, metadata) {
// Пример: логируем время кадра
// console.log(metadata.mediaTime, metadata.presentedFrames);

// Планируем следующий кадр, только если видео реально играет
if (!video.paused && !video.ended) {
frameHandle = video.requestVideoFrameCallback(onFrame);
}
}

video.addEventListener('play', () => {
frameHandle = video.requestVideoFrameCallback(onFrame);
});

video.addEventListener('pause', () => {
if (frameHandle !== null) {
video.cancelVideoFrameCallback(frameHandle);
frameHandle = null;
}
});

video.addEventListener('ended', () => {
if (frameHandle !== null) {
video.cancelVideoFrameCallback(frameHandle);
frameHandle = null;
}
});
```

Короткий вывод: всегда контролируйте подписки, иначе колбэк будет висеть впустую.

Шаг 2. Достаём полезные данные из metadata

Самое вкусное — объект `metadata`. В разных браузерах он может содержать чуть разный набор полей, но обычно есть:

- `mediaTime` — текущее время воспроизведения (секунды, с плавающей точкой);
- `presentedFrames` — сколько кадров уже показано;
- `expectedDisplayTime` — когда этот кадр попадёт на экран (наносекунды).

Пример извлечения:

```js
function onFrame(now, metadata) {
const time = metadata.mediaTime; // секунды
const frameCount = metadata.presentedFrames;

// Например, выводим в кастомный оверлей
overlay.textContent = `Кадр: ${frameCount}, время: ${time.toFixed(3)}s`;

frameHandle = video.requestVideoFrameCallback(onFrame);
}
```

Такой доступ к кадрам сильно упрощает оптимизацию воспроизведения видео на сайте: можно подстраивать анимацию, загрузку превью, подгрузку сегментов и т.п. именно тогда, когда это надо.

Синхронизация анимации и видео

Простая синхронизация эффектов

Самый частый сценарий — синхронизация анимации с видео JavaScript: подсветка субтитров, появление текста, анимационные элементы, привязанные к таймкодам.

Пусть у нас есть массив «ключевых точек»:

```js
const cues = [
{ time: 1.0, action: () => showCaption('Начинаем!') },
{ time: 3.5, action: () => highlightElement('#block1') },
{ time: 7.2, action: () => showCaption('Важный момент') },
];
```

Используем `requestVideoFrameCallback`, чтобы выполнять действия максимально точно по кадру:

```js
let nextCueIndex = 0;

function onFrame(now, metadata) {
const t = metadata.mediaTime;

while (nextCueIndex < cues.length && t >= cues[nextCueIndex].time) {
cues[nextCueIndex].action();
nextCueIndex++;
}

if (!video.paused && !video.ended) {
frameHandle = video.requestVideoFrameCallback(onFrame);
}
}
```

В отличие от таймеров, это не будет «гулять» при смене скорости воспроизведения или при паузах — всё жёстко привязано к фактическому `mediaTime`.

Привязка canvas-графики к видео

Другой популярный сценарий — отрисовывать поверх видео кастомную графику через ``. Например:

- рамка вокруг лица в видеопотоке;
- подсветка объекта;
- визуализация данных (графики, прогресс-бары).

Базовый пример:

```html


```

```js
const video = document.getElementById('video');
const canvas = document.getElementById('overlay');
const ctx = canvas.getContext('2d');

function resizeCanvas() {
canvas.width = video.clientWidth;
canvas.height = video.clientHeight;
}

window.addEventListener('resize', resizeCanvas);
video.addEventListener('loadedmetadata', resizeCanvas);

function onFrame(now, metadata) {
// Чистим холст
ctx.clearRect(0, 0, canvas.width, canvas.height);

// Пример: рисуем простую рамку, зависящую от времени
const t = metadata.mediaTime;
const padding = 20 + Math.sin(t) * 10;

ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)';
ctx.lineWidth = 4;
ctx.strokeRect(
padding,
padding,
canvas.width - padding * 2,
canvas.height - padding * 2
);

if (!video.paused && !video.ended) {
frameHandle = video.requestVideoFrameCallback(onFrame);
}
}
```

Так вы получаете плавную, кадр‑в‑кадр синхронизацию: рамка двигается или изменяется ровно в такт видео, без лагов.

Оптимизация: как не убить производительность

Использование requestVideoFrameCallback для работы с видео - иллюстрация

Если вы хотите, чтобы веб разработка и работа с видео плеером не превращались в борьбу с тормозами, нужно соблюдать пару простых правил.

Что не стоит делать в onFrame

Внутри колбэка не должно быть тяжёлой логики. Избегайте:

- сложных DOM‑операций (создание/удаление множества элементов);
- больших вычислений (распознавание лиц, сложная физика);
- синхронных запросов и блокирующего кода.

Правильнее:

- считать тяжёлое в `Web Worker` и в onFrame только читать результаты;
- батчить обновления DOM (менять текст/классы за один проход);
- использовать флаги, чтобы не делать одно и то же действие каждый кадр.

Минимальный «здоровый» паттерн

```js
let lastRenderedFrame = -1;

function onFrame(now, metadata) {
const frameNumber = metadata.presentedFrames;

// Если кадр тот же, можно ничего не делать
if (frameNumber === lastRenderedFrame) {
frameHandle = video.requestVideoFrameCallback(onFrame);
return;
}

lastRenderedFrame = frameNumber;

// Лёгкие операции обновления
overlay.textContent = `Кадр: ${frameNumber}`;

frameHandle = video.requestVideoFrameCallback(onFrame);
}
```

Короткий абзац, но важная мысль: не трогайте DOM и сложные структуры, если кадр не изменился.

Адаптация под слабые устройства

Иногда логично обрабатывать не каждый кадр. Например, видео 60 fps, а вам достаточно 15 fps для вычислений.

```js
const TARGET_FPS = 15;
let lastTime = 0;

function onFrame(now, metadata) {
const t = metadata.mediaTime;

if (t - lastTime >= 1 / TARGET_FPS) {
lastTime = t;

// ваша логика 15 fps
doLightWork(t);
}

frameHandle = video.requestVideoFrameCallback(onFrame);
}
```

Так вы снижаете нагрузку, но при этом остаетесь синхронизированными с реальным ходом видео.

Практические сценарии применения

1. Точный прогресс-бар и маркеры

Обычный прогресс‑бар завязан на `timeupdate`, и он может подёргиваться. С `requestVideoFrameCallback` вы обновляете полоску каждый кадр, делая интерфейс плавным.

Алгоритм:

1. В обработчике кадра берёте `mediaTime` и `duration`.
2. Считаете прогресс: `progress = mediaTime / duration`.
3. Применяете ширину/трансформации к индикатору.
4. В нужные моменты подсвечиваете маркеры (главы, рекламные блоки, главы курса).

2. Скролл, зависящий от видео

Использование requestVideoFrameCallback для работы с видео - иллюстрация

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

Простой паттерн:

- в `onFrame` считаете процент просмотра;
- по этому проценту обновляете CSS‑переменные;
- в стилях используете эти переменные для анимаций.

```js
function onFrame(now, metadata) {
const progress = metadata.mediaTime / video.duration;
document.documentElement.style.setProperty(
'--video-progress',
progress.toFixed(4)
);

frameHandle = video.requestVideoFrameCallback(onFrame);
}
```

А в CSS:

```css
.section {
opacity: calc(var(--video-progress) * 1.5);
}
```

3. Интерактивные обучающие видео

Для образовательного контента обработка видео в браузере JavaScript с точностью до кадра — прям подарок: можно показывать тестовые вопросы ровно в тот момент, когда в видео что‑то объяснили, затем останавливать ролик и ждать ответа.

Простой сценарий:

- заведите массив таймкодов вопросов;
- когда `mediaTime` переваливает за нужное значение — ставите видео на паузу, показываете модальное окно с вопросом;
- после ответа продолжаете воспроизведение и снова подписываетесь на кадры.

requestVideoFrameCallback + HTML5: реальная работа с видео

Если вы уже работали с `canvas`, `requestAnimationFrame` и знаете, как устроена JavaScript работа с видео HTML5, то переход на `requestVideoFrameCallback` будет логичным следующим шагом.

Ключевые выводы:

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

Небольшой чек-лист перед внедрением

Номерованный список для закрепления:

1. Проверяйте поддержку: добавьте условие, чтобы на старых браузерах всё не ломалось.
2. Не нагружайте onFrame: держите внутри только лёгкую логику и быстрые операции.
3. Привязывайте подписки к состоянию плеера: запускайте колбэк на `play`, останавливайте на `pause` и `ended`.
4. Тестируйте разные скорости проигрывания: убедитесь, что синхронизация работает и на 0.5x, и на 2x.
5. Следите за UX: если добавляете много визуальных эффектов, проверяйте, не проседает ли FPS на слабых устройствах.

Итог

Использование requestVideoFrameCallback для работы с видео - иллюстрация

`requestVideoFrameCallback` — это не «ещё один таймер», а недостающий кусок пазла для тех, кому нужна тонкая, детальная работа с видео в браузере.

Используя его вместе с аккуратным кодом и привычными инструментами веб‑разработки, вы получаете более точную веб разработку и работу с видео плеером: синхронные анимации, интерактивные подсказки, кастомные оверлеи и любое другое «волшебство» на уровне кадра.

Прокрутить вверх