Одной из задач, где мы в Ozon используем Canvas, является обработка видео. Для это у нас реализован минималистичный видеоредактор.
Современные устройства и браузеры развиваются достаточно быстро, но все-равно этого бывает недостаточно для задач со сложными вычислениями. К таким задачам можно отнести обработку видео.
Зачем нам видеоредактор
Есть два сценария, в которых мы можем использовать видеоредактор:
загрузка видео в карточки товаров;
внутренние сервисы для обучения сотрудников и продавцов.
Для этих сценариев необходимы следующие функции:
выбор обложки видео,
удаление звука,
редактирование длительности видео,
удаление сегментов видео.
Зачем нужно было делать свой редактор для веба и почему не использовать готовый? На эти вопросы достаточно просто ответить. Очень часто приложения для редактирования видео сложны в использовании и требуют специальных навыков. Вдобавок к этому это ещё одна точка входа для работы с контентом. Но почему в вебе? Тут тоже всё просто: функциональность для управления контентом доступна только там.
Выбор обложки для видео
После загрузки видео у пользователя должна быть возможность выбрать для него обложку. По умолчанию в качестве обложки устанавливается первый неоднородный кадр. Например, если первые пять секунд видео — это чёрный экран, то в качестве обложки будет предложен кадр, который появляется на шестой секунде.
Это подходит пользователям, которые не придают значения обложке видео. Но что, если где-то в середине вашего видео есть кадр, который, на ваш взгляд, лучше всего мотивирует аудиторию посмотреть ролик? Для таких случаев и нужна функция выбора обложки.
С точки зрения пользователя хочется иметь возможность выбрать в качестве обложки любой момент видео с точностью до миллисекунды. Но в этом случае появляется проблема с хранением и нарезкой картинок для прогресс-бара, по которому пользователь навигируется по видео. Нарезка картинок для каждой секунды, не говоря уже о более коротком промежутке времени, требует много места для хранения этих изображений. А так как пользователь может никогда не воспользоваться этой функцией, мы рискуем хранить большой объём ненужных данных.
Можно, конечно, готовить картинки по требованию, когда пользователь решит воспользоваться этой функцией, но, если видео достаточно длинное, это займёт много времени.
У этого подхода есть и другая проблема, связанная с количеством передаваемых данных. Если из часового видео нарезать изображений на каждую секунду, представляете, сколько пользователь потратит трафика? Я думаю, он будет этому не очень рад.
О реализации
В своей реализации мы используем три варианта оптимизации:
Адаптивная нарезка кадров в зависимости от длительности видео.
Формирование полотна из картинок.
Canvas для отображения видео в нескольких местах приложения.
Адаптивная нарезка кадров в зависимости от длительности видео
Такая детализация в нашем UI нужна в двух местах: превью активного кадра на прогресс-баре и большое превью в центре экрана. Весь остальной прогресс-бар не требует мелкой детализации. Главное для нас — показать пользователю ключевые моменты ролика с небольшой точностью. В нашем случае эта точность в среднем равна пяти секундам для больших видео и одной секунде — для маленьких.
Формирование полотна из картинок
Чтобы уменьшить количество запрашиваемых пользователем картинок, обычно прибегают к формированию полотен из нескольких изображений. Это позволяет уменьшить объёмы хранимой и передаваемой информации. Это уменьшение происходит потому, что контейнеры JPG, PNG и т. д., помимо самого изображения, хранят ещё и метаинформацию, необходимую для отображения картинки в данном формате. Меньше файлов — меньше метаинформации.
Полотно из картинок выглядит так:
После получения полотна использовать его для корректного отображения нужного кадра достаточно просто: нужно лишь применить CSS-свойство background-position.
Для отображения первого кадра нужно применить код ниже:
width: 58.8px;
height: 104px;
background: url(https://cdn1.ozone.ru/P0.jpg) 0px 0px / 117.6px 208px;
В результате мы отобразим кадр, обведённый красным:
А для четвёртого кадра нужно сделать вот так:
width: 58.8px;
height: 104px;
background: url(https://cdn1.ozone.ru/P0.jpg) -58.8px -104px / 117.6px 208px;
Тут видно, что мы указываем в свойство background смещение по горизонтали 58,8 px, равное ширине кадра, а по вертикали — 104 px, равное высоте кадра.
Количество картинок на полотне может быть разным, оно зависит от задачи. У нас реализован универсальный механизм, который позволяет вместе с полотном получать JSON-файл с информацией о количестве кадров на нём по горизонтали и вертикали. Это даёт возможность на лету менять эти параметры при необходимости.
Canvas для отображения видео в нескольких местах приложения
Мы уже поняли, что очень детализированное отображение видео в таймлайне нам не нужно. А вот в случае с активным кадром очень хотелось бы иметь такое.
Уу нас есть два места, в которых отображается активный кадр: большое превью и миниатюра на прогресс-баре. В этих местах мы отображаем реальное видео, которое перематываем на конкретную секунду в зависимости от действий пользователя. Основная проблема такого подхода в том, что нам нужно воспроизводить видео сразу в двух местах и желательно с одинаковой задержкой. Для этого мы используем Canvas API, который позволяет рестримить видео на холст. Этим мы достигаем одинакового времени воспроизведения и уменьшения количества сетевых запросов. Мы даже в кеш не ходим, а просто рендерим кадр дважды. Если вы думаете, что это сложно, то вы ошибаетесь. Canvas API позволяет это делать буквально в одну строку.
const canvasRef: HTMLCanvasElement = this.$refs.preview;
const videoRef: HTMLVideoElement = this.$refs.video;
const context: CanvasRenderingContext2D = canvasRef.getContext('2d');
context.drawImage(videoRef, 0, 0, canvasRef.width, canvasRef.height);
Работа с сегментами видео
Самая сложная функциональность нашего видеоредактора связана с логиком по работе с сегментами (отрезками) видео.
Она состоит из нескольких опций:
Изменение длительности видео.
Удаление сегментов в любой части ролика.
Удаление звука.
Интерфейс выглядит так:
Есть три блока для работы с видео:
Список сегментов.
Плеер с текущим видео.
Таймлайн для взаимодействия с конкретным моментом ролика.
Список сегментов
Этот блок предоставляет возможности добавлять новые сегменты и редактировать существующие.
Механика редактирования сегментов максимально проста. Есть два текстовых поля для начала и окончания отрезка видео с возможностью указывать время с очень большой точностью.
Таймлайн для взаимодействия с конкретным моментом ролика
Таймлайн обеспечивает визуальное представление действий пользователя. Синий ползунок указывает на начало видео, его можно смещать в сторону. Красные области обозначают сегменты ролика, которые будут удалены. Каждый сегмент можно редактировать, перемещая ползунки начала и конца влево и вправо. Также редактировать можно в блоке сегментов.
С точки зрения реализации самое сложное — это отображение большого количества изображений. Так как видео может быть достаточно большим, их может быть очень много.
Для уменьшения количества запросов и объёма передаваемых данных мы, так же, как и в компоненте, который рассматривали в предыдущем разделе, используем механизм с полотнами, но применяем ещё одну оптимизацию. Она связана с количеством элементов в DOM-дереве. Поскольку изображений может быть очень много, мы отображаем полученные полотна в одном теге Canvas, располагая их друг за другом по горизонтали. Соответственно, при изменении масштаба таймлайна кнопками скейл в UI мы просто меняем размер изображений, которые рисуем в Сanvas. Благодаря этому за отображение таймлайна у нас всегда отвечает всего один DOM-элемент.
Вывод
Объединяя изображения в одно, мы выигрываем в количестве запросов, объёмах передаваемых ресурсов и пространства для хранения данных. А Canvas помогает нам минимизировать затраты на воспроизведение видео и сводит к минимуму количество DOM-элементов. Таким образом, используя перечисленные приемы, можно достичь хороших показателей производительности.