Разработка онлайн-редактора видео
Отметим: когда клиент просит сделать монтаж видео прямо в браузере — первое, с чем сталкивается команда: рендеринг финального результата. Если одну минуту ролика можно обработать на клиенте через FFmpeg.wasm, то десятиминутное видео «зависнет» даже на мощном ноутбуке. Мы прошли этот путь на нескольких проектах и выработали архитектуру, которая работает для любого масштаба. В этой статье разберём ключевые архитектурные решения: от типа рендеринга до реализации таймлайна и загрузки файлов. Вы узнаете, как выбрать стек, чтобы не переплачивать за серверы и не терять пользователей из-за тормозов. Мы также рассмотрим типичные ошибки, такие как создание тысяч запросов на один проект, и как их избежать. Если планируете создать браузерный видеоредактор — эта статья станет вашей архитектурной картой.
Браузер vs Сервер: где рендерить?
Выбор между клиентским и серверным рендерингом определяет весь стек и UX. Рассмотрим оба варианта в таблице.
| Параметр | Client-side (WebCodecs + FFmpeg.wasm) | Server-side (Remotion + FFmpeg) |
|---|---|---|
| Зависимость от CPU пользователя | Да | Нет |
| Максимальная длительность видео | ~2 мин | Не ограничена |
| Время рендеринга 10 мин (CPU i7) | 5–15 мин | ~2 мин на GPU |
| Масштабирование | Одно видео за раз | Параллельные задачи (Lambda) |
| Поддержка браузеров | Chrome 94+, Firefox 130+, Safari 16.4+ | Любой браузер (файл по ссылке) |
| Стоимость инфраструктуры | Нулевая | Затраты на сервер (экономия до 40% при высокой нагрузке) |
Client-side рендеринг (WebCodecs API + FFmpeg.wasm): не требует серверных мощностей, вся нагрузка на CPU пользователя. FFmpeg.wasm рендерит 10 минут видео за 5–15 минут реального времени (зависит от CPU). WebCodecs API поддерживается в Chrome 94+, Firefox 130+, Safari 16.4+. Идеален для коротких клипов до 2 минут — без задержек, без серверных затрат.
Server-side рендеринг (Remotion + FFmpeg): рендеринг на серверах с GPU, пользователь получает готовый файл по ссылке. Remotion позволяет описывать видео как React-компоненты, что даёт гибкость и переиспользование. Масштабируется через AWS Lambda — рендеринг параллельными чанками. Для коммерческого продукта с видео более 2 минут — это единственный надёжный путь. Мы в проектах чаще выбираем серверный подход: он даёт предсказуемое время рендеринга и не «съедает» батарею пользователя. Но в минимальном редакторе для коротких роликов клиентский вариант быстрее и дешевле.
Remotion официальная документация: https://remotion.dev/docs/
Как устроена временная шкала (таймлайн)?
Центральная UI-концепция — таймлайн с дорожками (треками). Структура данных:
interface VideoProject {
id: string;
duration: number; // секунды
fps: number; // 24 | 30 | 60
width: number;
height: number;
tracks: Track[];
}
interface Track {
id: string;
type: 'video' | 'audio' | 'text' | 'image' | 'effect';
clips: Clip[];
muted: boolean;
locked: boolean;
volume: number; // 0–1
}
interface Clip {
id: string;
trackId: string;
assetId: string; // ссылка на загруженный файл
startTime: number; // позиция на таймлайне (секунды)
duration: number; // длительность клипа
trimStart: number; // обрезка начала исходного файла
trimEnd: number; // обрезка конца
speed: number; // 0.25 – 4.0
opacity: number;
transform?: ClipTransform;
filters?: VideoFilter[];
}
Данные хранятся в сторе (Zustand или Redux) и синхронизируются с базой при сохранении. Drag-and-drop реализуем через @dnd-kit — он надёжно работает с перетаскиванием клипов по трекам.
Как разрезать клип на таймлайне?
Split (разрезать клип) — одна из базовых операций. Вот пошаговый алгоритм:
- Определите точку разреза в секундах относительно таймлайна (например, при перетаскивании маркера).
- Рассчитайте splitPoint = (atTime - clip.startTime) + clip.trimStart.
- Создайте два новых клипа: левый с trimEnd = splitPoint и правый с trimStart = splitPoint.
- Удалите исходный клип и добавьте новые.
Код:
const splitClip = (clipId: string, atTime: number) => {
const clip = getClip(clipId);
const splitPoint = atTime - clip.startTime + clip.trimStart;
const leftClip: Clip = { ...clip, id: uuid(), duration: atTime - clip.startTime, trimEnd: splitPoint };
const rightClip: Clip = {
...clip,
id: uuid(),
startTime: atTime,
duration: clip.startTime + clip.duration - atTime,
trimStart: splitPoint,
};
removeClip(clipId);
addClip(leftClip);
addClip(rightClip);
};
Предпросмотр в браузере
Для предпросмотра используем HTML5 <video> с синхронизацией через currentTime. Вот ключевой hook:
const PreviewPlayer: React.FC = () => {
const { currentTime, isPlaying, tracks } = useEditorStore();
const videoRefs = useRef<Map<string, HTMLVideoElement>>(new Map());
useEffect(() => {
// Синхронизируем все видео-клипы с таймлайном
tracks.forEach(track => {
track.clips.forEach(clip => {
const video = videoRefs.current.get(clip.id);
if (!video) return;
const clipTime = currentTime - clip.startTime;
const isActive = clipTime >= 0 && clipTime <= clip.duration;
video.style.display = isActive ? 'block' : 'none';
if (isActive) {
const targetTime = clip.trimStart + clipTime * clip.speed;
if (Math.abs(video.currentTime - targetTime) > 0.05) {
video.currentTime = targetTime;
}
isPlaying ? video.play() : video.pause();
} else {
video.pause();
}
});
});
}, [currentTime, isPlaying]);
// ...
};
Скроббинг по таймлайну делаем через mousedown/mousemove — расчёт позиции в секундах от ширины контейнера.
Клипы: drag, trim, split
Перетаскивание: при drag клипа вычисляем дельту и обновляем startTime с учётом пикселей на секунду. Ограничиваем границами проекта и соседними клипами.
Trim (обрезка) реализуется аналогично split, но без удаления исходного клипа: достаточно изменить trimStart/trimEnd.
Серверный рендеринг через Remotion
Remotion позволяет описать видео как React-компонент. Передаём проект через inputProps и рендерим на сервере:
// Компонент для рендеринга
const VideoComposition: React.FC<{ project: VideoProject }> = ({ project }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const currentTime = frame / fps;
return (
<AbsoluteFill style={{ background: '#000' }}>
{project.tracks.flatMap(track =>
track.clips.map(clip => {
const clipTime = currentTime - clip.startTime;
if (clipTime < 0 || clipTime > clip.duration) return null;
return (
<OffthreadVideo
key={clip.id}
src={clip.assetUrl}
startFrom={Math.round(clip.trimStart * fps)}
style={{ opacity: clip.opacity }}
/>
);
})
)}
</AbsoluteFill>
);
};
Запуск рендеринга через API с использованием renderMediaOnLambda для масштабирования.
Загрузка ассетов
Крупные файлы (видео, аудио) загружаем напрямую в S3 через presigned URL — минуем сервер приложения:
// 1. Запрашиваем presigned URL
const { uploadUrl, key } = await api.post('/api/editor/upload-url', {
filename: file.name,
contentType: file.type,
size: file.size,
});
// 2. Загружаем напрямую в S3 с прогрессом
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (e) => {
setProgress(Math.round(e.loaded / e.total * 100));
});
xhr.open('PUT', uploadUrl);
xhr.setRequestHeader('Content-Type', file.type);
xhr.send(file);
Этот подход разгружает сервер и повышает скорость загрузки. Свяжитесь с нами, чтобы обсудить детали вашего проекта.
Сроки и что входит в работу
Минимальный редактор (таймлайн, обрезка, предпросмотр, экспорт через Remotion) — от 13 до 17 рабочих дней. Полный функционал с эффектами, переходами и аудиомикшером — 20–25 дней. Стоимость базового решения стартует от 300 000 руб., а затраты на серверную инфраструктуру — от $200 в месяц.
Отметим: Что входит в результат:
- Архитектура и код проекта с документацией API.
- Инструкция по развёртыванию (Docker Compose, .env).
- Доступы к репозиторию и CI/CD.
- 12 месяцев гарантии на код и поддержка при внедрении.
Ориентировочная стоимость рассчитывается индивидуально после брифа. Получите консультацию для оценки вашего проекта — мы подготовим смету и сроки за один рабочий день.
| Этап | Время |
|---|---|
| Таймлайн (drag, trim, split) | 4–5 дней |
| Предпросмотр (синхронизация видео) | 3–4 дня |
| Загрузка ассетов (S3 presigned) | 1 день |
| Текстовые оверлеи, изображения | 2–3 дня |
| Remotion рендеринг + статус задачи | 3–4 дня |
| Аудио (громкость, mute, fade) | 2 дня |
| Эффекты и фильтры (brightness, contrast) | 2–3 дня |
Минимальный редактор (без эффектов и сложных фильтров): 13–17 рабочих дней. Закажите разработку под ключ — мы учтём все ваши требования.







