Разработка онлайн-редактора видео
Отметим: когда клиент просит сделать монтаж видео прямо в браузере — первое, с чем сталкивается команда: рендеринг финального результата. Если одну минуту ролика можно обработать на клиенте через 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 рабочих дней. Закажите разработку под ключ — мы учтём все ваши требования.







