Обработка видео в браузере с помощью FFmpeg.wasm и Solidjs

Привет, разработчики, в этой статье я покажу вам, как вы можете использовать ffmpeg в браузере, используя его WASM привязку с SolidJS.

Согласно WASM

WebAssembly (сокращенно Wasm) — это формат двоичных инструкций для виртуальной машины на основе стека. Wasm разработан как переносимая цель компиляции для языков программирования, позволяющая развертывать в Интернете клиентские и серверные приложения.

FFmpeg

FFmpeg — это кроссплатформенное решение для записи, преобразования и потоковой передачи аудио и видео.

FFmpeg.wasm

ffmpeg.wasm — это чистый Webassembly / Javascript порт FFmpeg. Он позволяет записывать, конвертировать и передавать видео и аудио прямо в браузере.

SolidJS

SolidJS — это реактивная, исполнительная, мощная, прагматичная и продуктивная библиотека Javascript.

В этом посте мы создадим хук useFFmpeg, который будет конвертировать видеофайл в mp4 и воспроизводить его в браузере. Мы разработаем это веб-приложение, используя Hope ui.

Создайте новый проект solidjs и установите зависимости.

> npx degit solidjs/templates/js ffmpeg-solidjs
> cd ffmpeg-solidjs
> pnpm i
> pnpm run dev 

Войдите в полноэкранный режим Выйдите из полноэкранного режима

Добавьте Hope ui в проект solidjs

> pnpm add @hope-ui/solid @stitches/core solid-transition-group

Войдите в полноэкранный режим Выйдите из полноэкранного режима

Добавить зависимость FFmpeg.wasm

> pnpm add @ffmpeg/ffmpeg

Войти в полноэкранный режим Выйти из полноэкранного режима

Создадим файл useFFmpeg.jsx внутри src/hooks.

import { createStore } from "solid-js/store";
import { onCleanup } from "solid-js";
import { createFFmpeg, fetchFile } from "@ffmpeg/ffmpeg";

export default function useFFmpeg() {
  const [store, setStore] = createStore({
    progress: null, 
    videoURL: null,
  });

  const ffmpeg = createFFmpeg({ progress: (e) => setStore("progress", e) });

  const transcode = async (file) => {
    const { name } = file;
    // load ffmpeg.wasm code 
    await ffmpeg.load();
    // write file to  memory filesystem 
    ffmpeg.FS("writeFile", name, await fetchFile(file));
    // convert video into mp4 
    await ffmpeg.run("-i", name, "output.mp4");
    // read file from Memory filesystem 
    const data = ffmpeg.FS("readFile", "output.mp4");
    const url = URL.createObjectURL(
      new Blob([data.buffer], { type: "video/mp4" })
    );

    setStore("videoURL", url);
    setStore("progress", null);
  };

  const handleFileChange = (e) => {
    // start video conversion on file change 
    transcode(e.target.files[0]);
  };

  onCleanup(() => {
    // revoke created blob url object 
    URL.revokeObjectURL(store.videoURL)
  })
  return {
    store,
    handleFileChange,
  };
}


Вход в полноэкранный режим Выход из полноэкранного режима

Функция createFFmpeg() будет инициировать ffmpeg и обрабатывать логирование всех деталей во время работы с видео, отслеживать прогресс работы с видео и т.д..

Прогресс видео будет храниться в реактивном хранилище solidjs, чтобы мы могли использовать это хранилище для выполнения реактивных действий.

ffmpeg.load() — это асинхронный метод, который загрузит ядро ffmpeg и инициализирует бинарный файл ffmpeg wasm.

ffmpeg.FS() — вспомогательный метод, который поможет работать с файловой системой памяти в браузере.

URL.createObjectURL() статический метод, создающий blob-объект url, который вы можете напрямую использовать внутри нативного элемента html, например <video>.

После создания blob-объекта url видео мы сохраним в reactive store, чтобы использовать его для воспроизведения видео.

Когда компонент будет размонтирован, мы очистим память, занятую объектом blob, используя статический метод URL.revokeObjectURL().

Теперь подключим хук useFFmpeg внутри App.jsx.

import {
  Box,
  Button,
  CircularProgress,
  CircularProgressIndicator,
  CircularProgressLabel,
  Container,
  Flex,
  GridItem,
  Heading,
  HopeProvider,
  HStack,
  SimpleGrid,
  Text,
  VStack,
} from "@hope-ui/solid";
import useFFmpeg from "./hooks/useFFmpeg";
import { Show } from "solid-js";
function App() {
  let fileRef;
  const { store, handleFileChange } = useFFmpeg();
  return (
    <HopeProvider config={{ initialColorMode: "dark" }}>
      <Container minH={"100vh"} display="grid" placeItems={"center"}>
        <Box py={"$4"}>
          <Heading fontSize={"$4xl"} textAlign="center">
            Video processing in browser using
            <Box>
              <Text as="span" color={"$success10"}>
                FFmpeg{" "}
              </Text>
              and{" "}
              <Text as="span" color={"$primary10"}>
                SolidJS
              </Text>
            </Box>
          </Heading>

          <HStack justifyContent={"center"} mt={"$6"}>
            <input
              type="file"
              name="file"
              id="file"
              hidden
              onChange={[handleFileChange]}
              ref={fileRef}
            />
            <Button onClick={() => fileRef.click()}>Select Video File</Button>
          </HStack>

          <Show when={store.progress}>
            <SimpleGrid columns={{ "@initial": 1, "@sm": 2, "@md": 2,"@lg":3 }} justifyContent={"center"} alignItems="center" mt={"$6"} spacing={"$4"}>
              <GridItem mx="auto">
                <CircularProgress
                  value={Math.round(store.progress?.ratio * 100)}
                  size={"$52"}
                >
                  <CircularProgressIndicator color="$success10" />
                  <CircularProgressLabel>
                    <VStack spacing={"$2"}>
                      <Heading fontSize={"$xl"}>Ratio</Heading>
                      <Heading fontSize={"$3xl"}>
                        {Math.round(store.progress?.ratio * 100)} %
                      </Heading>
                    </VStack>
                  </CircularProgressLabel>
                </CircularProgress>
              </GridItem>

              <GridItem mx="auto">
                <CircularProgress value={100} size={"$52"} thickness={"$1"}>
                  <CircularProgressIndicator color="$success10" />
                  <CircularProgressLabel>
                    <VStack spacing={"$2"}>
                      <Heading fontSize={"$xl"}>Duration</Heading>
                      <Heading fontSize={"$3xl"}>
                        {store.progress?.duration}
                      </Heading>
                    </VStack>
                  </CircularProgressLabel>
                </CircularProgress>
              </GridItem>

              <GridItem mx="auto">
                <CircularProgress size={"$52"} indeterminate>
                  <CircularProgressIndicator color="$primary10" />
                  <CircularProgressLabel>
                    <VStack spacing={"$2"}>
                      <Heading fontSize={"$xl"}>Time</Heading>
                      <Heading fontSize={"$3xl"}>{store.progress?.time}</Heading>
                    </VStack>
                  </CircularProgressLabel>
                </CircularProgress>
              </GridItem>
            </SimpleGrid>
          </Show>

          <Show when={store.videoURL}>
            <Flex justifyContent={"center"} mt={"$6"}>
              <video
                src={store.videoURL}
                width={"400px"}
                height={"400px"}
                autoPlay
                controls
              />
            </Flex>
          </Show>

        </Box>



      </Container>
    </HopeProvider>
  );
}

export default App;


Вход в полноэкранный режим Выход из полноэкранного режима

Напоследок мы настроим vite на обработку кросс-оригинальной изоляции, добавив Cross-Origin-Embedder-Policy и


import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";

export default defineConfig({
  plugins: [
    solidPlugin(),
    {
      name: "configure-response-headers",
      configureServer: (server) => {
        server.middlewares.use((_req, res, next) => {
          res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
          res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
          next();
        });
      },
    },
  ],
  build: {
    target: "esnext",
    polyfillDynamicImport: false,
  },
});



Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь запустите ваш dev-сервер и выберите любой видеофайл.

pnpm run dev

Войти в полноэкранный режим Выход из полноэкранного режима

Использование

Эти зависимости шаблонов поддерживаются через pnpm посредством pnpm up -Lri.

Именно по этой причине вы видите pnpm-lock.yaml. Тем не менее, любой менеджер пакетов будет работать. Этот файл можно смело удалить после клонирования шаблона.

$ npm install # or pnpm install or yarn install
Вход в полноэкранный режим Выход из полноэкранного режима

Узнайте больше на сайте Solid и пообщайтесь с нами в Discord.

Доступные скрипты

В директории проекта вы можете запустить:

Запускает приложение в режиме разработки.
Откройте http://localhost:3000 для просмотра в браузере.

Страница перезагрузится, если вы внесете изменения.

Собирает приложение для производства в папку dist.
Он корректно связывает Solid в режиме производства и оптимизирует сборку для наилучшей производительности.

Сборка минифицирована, а имена файлов включают хэши.
Ваше приложение готово к развертыванию!

Развертывание

Вы можете…

Посмотреть на GitHub

Оцените статью
Procodings.ru
Добавить комментарий