Разработка онлайн-редактора видео

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка онлайн-редактора видео
Сложная
от 2 недель до 3 месяцев
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Разработка онлайн-редактора видео

Браузерный видеоредактор — одна из технически тяжёлых задач во фронтенд-разработке. Ключевое решение на старте: где происходит рендеринг финального видео — в браузере или на сервере. От этого зависит весь стек.

Браузер vs Сервер: где рендерить

Client-side рендеринг (WebCodecs API + FFmpeg.wasm):

  • Не требует серверных мощностей для рендеринга
  • Ограничен производительностью CPU пользователя
  • FFmpeg.wasm: 10 минут видео рендерятся ~5–15 минут
  • Поддержка WebCodecs: Chrome 94+, Firefox 130+, Safari 16.4+

Server-side рендеринг (Remotion + FFmpeg):

  • Рендеринг на мощных серверах (GPU опционально)
  • Пользователь не ждёт — получает готовый файл по ссылке
  • Remotion умеет рендерить React-компоненты в видео
  • Хорошо масштабируется через AWS Lambda

Для коммерческого продукта с продолжительными видео — серверный рендеринг. Для лёгкого инструмента (короткие клипы до 2 минут) — клиентский.

Временная шкала (Timeline)

Центральная 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[];
}

Предпросмотр в браузере

Для предпросмотра до рендеринга используем HTML5 <video> с синхронизацией через currentTime:

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]);

  // ...
};

Скрабbing по таймлайну:

const Timeline: React.FC = () => {
  const { setCurrentTime, duration } = useEditorStore();
  const railRef = useRef<HTMLDivElement>(null);

  const handleMouseDown = (e: React.MouseEvent) => {
    const handleMove = (e: MouseEvent) => {
      const rect = railRef.current!.getBoundingClientRect();
      const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
      setCurrentTime(pct * duration);
    };
    document.addEventListener('mousemove', handleMove);
    document.addEventListener('mouseup', () => {
      document.removeEventListener('mousemove', handleMove);
    }, { once: true });
  };

  return (
    <div ref={railRef} className="timeline-rail" onMouseDown={handleMouseDown}>
      <TimelinePlayhead />
      {/* clips... */}
    </div>
  );
};

Клипы: drag, trim, split

Drag по таймлайну через @dnd-kit или кастомный drag:

const handleClipDrag = (clipId: string, deltaX: number, pxPerSecond: number) => {
  const deltaSeconds = deltaX / pxPerSecond;
  updateClip(clipId, clip => ({
    ...clip,
    startTime: Math.max(0, clip.startTime + deltaSeconds),
  }));
};

Split (разрезать клип):

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);
};

Серверный рендеринг через Remotion

Remotion позволяет описывать видео как React-компоненты:

// Компонент для рендеринга
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:

// Backend: POST /api/render
import { renderMedia, selectComposition } from '@remotion/renderer';

const render = async (projectData: VideoProject) => {
  const composition = await selectComposition({
    serveUrl,
    id: 'VideoComposition',
    inputProps: { project: projectData },
  });

  await renderMedia({
    composition,
    serveUrl,
    codec: 'h264',
    outputLocation: `out/${projectData.id}.mp4`,
    imageFormat: 'jpeg',
  });
};

Для масштабирования — Remotion Lambda рендерит параллельно чанками:

import { renderMediaOnLambda } from '@remotion/lambda';

const { renderId } = await renderMediaOnLambda({
  functionName: 'remotion-render',
  region: 'eu-central-1',
  serveUrl,
  composition: 'VideoComposition',
  inputProps: { project: projectData },
  codec: 'h264',
});

Загрузка ассетов

Крупные видеофайлы загружаем напрямую в 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);

Сроки

Этап Время
Таймлайн (drag, trim, split) 4–5 дней
Предпросмотр (синхронизация видео) 3–4 дня
Загрузка ассетов (S3 presigned) 1 день
Текстовые оверлеи, изображения 2–3 дня
Remotion рендеринг + статус задачи 3–4 дня
Аудио (громкость, mute, fade) 2 дня
Эффекты и фильтры (brightness, contrast) 2–3 дня

Минимальный редактор (без эффектов и сложных фильтров): 13–17 рабочих дней.