Hi, Developers in this blog post i will show you how you can utilize ffmpeg in browser using their WASM binding with SolidJS.
According to WASM
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
FFmpeg
FFmpeg is a cross-platform solution to record, convert and stream audio and video.
FFmpeg.wasm
ffmpeg.wasm is a pure Webassembly / Javascript port of FFmpeg. It enables video & audio record, convert and stream right inside browsers.
SolidJS
Solidjs is a Reactive, Performant, Powerful, Pragmatic and Productive Javascript library.
In this post we will create a hook useFFmpeg
hook that will handle to convert a video file into mp4
and play inside browser. We will design this web app using Hope ui
.
Create new solidjs project and install dependencies.
> npx degit solidjs/templates/js ffmpeg-solidjs
> cd ffmpeg-solidjs
> pnpm i
> pnpm run dev
Add Hope ui in solidjs project
> pnpm add @hope-ui/solid @stitches/core solid-transition-group
Add FFmpeg.wasm dependency
> pnpm add @ffmpeg/ffmpeg
Let's create useFFmpeg.jsx
file inside 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()
function will init ffmpeg and handle logging all the details during video manipulation , track progress of video manipulation etc..
Video progress will be stored in a reactive solidjs store so that we can utilize this store to do reactive stuff.
ffmpeg.load()
is an async method that will download core ffmpeg and init ffmpeg wasm binary.
ffmpeg.FS()
is a helper method that will help to work around memory filesystem in browser.
URL.createObjectURL()
static method create blob object url that you can directly use inside html native element like <video>
After creating blob object url of video we will store inside reactive store so that we can utilize this to play video.
when component will unmount we will clear blob object occupied memory using URL.revokeObjectURL()
static method.
Now we will connect useFFmpeg
hook inside 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;
In last we will configure vite to handle cross origin isolation by adding Cross-Origin-Embedder-Policy
and
Cross-Origin-Opener-Policy
headers
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,
},
});
Now start your dev server and select any video file.
pnpm run dev
harshmangalam / ffmpeg-solidjs
Video processing in browser using FFmpeg and SolidJS
Usage
Those templates dependencies are maintained via pnpm via pnpm up -Lri
.
This is the reason you see a pnpm-lock.yaml
. That being said, any package manager will work. This file can be safely be removed once you clone a template.
$ npm install # or pnpm install or yarn install
Learn more on the Solid Website and come chat with us on our Discord
Available Scripts
In the project directory, you can run:
npm dev
or npm start
Runs the app in the development mode.
Open http://localhost:3000 to view it in the browser.
The page will reload if you make edits.
npm run build
Builds the app for production to the dist
folder.
It correctly bundles Solid in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
Your app is ready to be deployed!
Deployment
You canβ¦
Top comments (2)
Is there a hosted version we can try?
I have not hosted this in production environment.