diff --git a/apps/website/docs/api/classes/FFmpeg.md b/apps/website/docs/api/classes/FFmpeg.md index faf0400..5d5fa9a 100644 --- a/apps/website/docs/api/classes/FFmpeg.md +++ b/apps/website/docs/api/classes/FFmpeg.md @@ -28,7 +28,7 @@ const ffmpeg = new FFmpeg(); #### Defined in -[packages/ffmpeg/src/classes.ts:97](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L97) +[packages/ffmpeg/src/classes.ts:99](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L99) ## Properties @@ -38,7 +38,7 @@ const ffmpeg = new FFmpeg(); #### Defined in -[packages/ffmpeg/src/classes.ts:95](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L95) +[packages/ffmpeg/src/classes.ts:95](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L95) ___ @@ -51,7 +51,7 @@ be called when we receive message from web worker. #### Defined in -[packages/ffmpeg/src/classes.ts:94](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L94) +[packages/ffmpeg/src/classes.ts:94](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L94) ___ @@ -61,7 +61,17 @@ ___ #### Defined in -[packages/ffmpeg/src/classes.ts:88](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L88) +[packages/ffmpeg/src/classes.ts:88](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L88) + +___ + +### loaded + +• **loaded**: `boolean` = `false` + +#### Defined in + +[packages/ffmpeg/src/classes.ts:97](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L97) ___ @@ -121,7 +131,7 @@ node_modules/@types/node/events.d.ts:290 #### Defined in -[packages/ffmpeg/src/classes.ts:84](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L84) +[packages/ffmpeg/src/classes.ts:84](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L84) ___ @@ -131,7 +141,7 @@ ___ #### Defined in -[packages/ffmpeg/src/classes.ts:85](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L85) +[packages/ffmpeg/src/classes.ts:85](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L85) ___ @@ -141,7 +151,7 @@ ___ #### Defined in -[packages/ffmpeg/src/classes.ts:86](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L86) +[packages/ffmpeg/src/classes.ts:86](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L86) ## Event Methods @@ -172,7 +182,7 @@ ffmpeg.on(FFmpeg.DOWNLOAD, ({ url, total, received, delta, done }) => { #### Defined in -[packages/ffmpeg/src/classes.ts:33](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L33) +[packages/ffmpeg/src/classes.ts:33](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L33) ▸ **on**(`event`, `listener`): [`FFmpeg`](FFmpeg.md) @@ -203,7 +213,7 @@ log includes output to stdout and stderr. #### Defined in -[packages/ffmpeg/src/classes.ts:52](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L52) +[packages/ffmpeg/src/classes.ts:52](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L52) ▸ **on**(`event`, `listener`): [`FFmpeg`](FFmpeg.md) @@ -235,7 +245,7 @@ input and output video/audio file are the same. #### Defined in -[packages/ffmpeg/src/classes.ts:69](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L69) +[packages/ffmpeg/src/classes.ts:69](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L69) ___ @@ -278,7 +288,7 @@ const data = ffmpeg.readFile("video.mp4"); #### Defined in -[packages/ffmpeg/src/classes.ts:197](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L197) +[packages/ffmpeg/src/classes.ts:202](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L202) ___ @@ -303,7 +313,7 @@ as it initializes WebAssembly and other essential variables. #### Defined in -[packages/ffmpeg/src/classes.ts:166](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L166) +[packages/ffmpeg/src/classes.ts:171](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L171) ___ @@ -320,7 +330,7 @@ Terminate all ongoing API calls and terminate web worker. #### Defined in -[packages/ffmpeg/src/classes.ts:218](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L218) +[packages/ffmpeg/src/classes.ts:223](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L223) ___ @@ -344,7 +354,7 @@ Create a directory. #### Defined in -[packages/ffmpeg/src/classes.ts:315](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L315) +[packages/ffmpeg/src/classes.ts:321](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L321) ___ @@ -366,7 +376,7 @@ Delete an empty directory. #### Defined in -[packages/ffmpeg/src/classes.ts:337](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L337) +[packages/ffmpeg/src/classes.ts:343](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L343) ___ @@ -388,13 +398,13 @@ Delete a file. #### Defined in -[packages/ffmpeg/src/classes.ts:293](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L293) +[packages/ffmpeg/src/classes.ts:299](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L299) ___ ### listDir -▸ **listDir**(`path`): `Promise`<`FFFSPaths`\> +▸ **listDir**(`path`): `Promise`<`FSNode`[]\> List directory contents. @@ -406,11 +416,11 @@ List directory contents. #### Returns -`Promise`<`FFFSPaths`\> +`Promise`<`FSNode`[]\> #### Defined in -[packages/ffmpeg/src/classes.ts:326](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L326) +[packages/ffmpeg/src/classes.ts:332](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L332) ___ @@ -441,7 +451,7 @@ const data = await ffmpeg.readFile("video.mp4"); #### Defined in -[packages/ffmpeg/src/classes.ts:272](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L272) +[packages/ffmpeg/src/classes.ts:278](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L278) ___ @@ -464,7 +474,7 @@ Rename a file or directory. #### Defined in -[packages/ffmpeg/src/classes.ts:304](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L304) +[packages/ffmpeg/src/classes.ts:310](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L310) ___ @@ -496,7 +506,7 @@ await ffmpeg.writeFile("text.txt", "hello world"); #### Defined in -[packages/ffmpeg/src/classes.ts:246](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L246) +[packages/ffmpeg/src/classes.ts:252](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L252) ___ @@ -514,7 +524,7 @@ register worker message event handlers. #### Defined in -[packages/ffmpeg/src/classes.ts:104](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L104) +[packages/ffmpeg/src/classes.ts:106](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L106) ___ @@ -537,7 +547,7 @@ Generic function to send messages to web worker. #### Defined in -[packages/ffmpeg/src/classes.ts:143](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/1582ee2/packages/ffmpeg/src/classes.ts#L143) +[packages/ffmpeg/src/classes.ts:148](https://github.com/ffmpegwasm/ffmpeg.wasm/blob/4a950d7/packages/ffmpeg/src/classes.ts#L148) ___ diff --git a/apps/website/src/components/Playground/CoreSelector.tsx b/apps/website/src/components/Playground/CoreSelector.tsx deleted file mode 100644 index fd47da7..0000000 --- a/apps/website/src/components/Playground/CoreSelector.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as React from "react"; -import Button from "@mui/material/Button"; -import Container from "@mui/material/Container"; -import Radio from "@mui/material/Radio"; -import RadioGroup from "@mui/material/RadioGroup"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import FormControl from "@mui/material/FormControl"; -import FormLabel from "@mui/material/FormLabel"; -import styles from "./styles.module.css"; - -interface CoreSelectorProps { - option: string; - onChange: (event: React.ChangeEvent) => any; - onSubmit: () => any; -} -export default function CoreSelector({ - option, - onChange, - onSubmit, -}: CoreSelectorProps) { - return ( - - - - Select a Core Option - - } - label="Core (Slower, but stable)" - /> - } - label="Core Multi-threaded (Faster, but lower compatibility and unstable)" - /> - - - - - - - - ); -} diff --git a/apps/website/src/components/Playground/CoreSwitcher.tsx b/apps/website/src/components/Playground/CoreSwitcher.tsx new file mode 100644 index 0000000..50596c4 --- /dev/null +++ b/apps/website/src/components/Playground/CoreSwitcher.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import Stack from "@mui/material/Stack"; +import FormGroup from "@mui/material/FormGroup"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Switch from "@mui/material/Switch"; +import IconButton from "@mui/material/IconButton"; +import HelpIcon from "@mui/icons-material/HelpOutline"; +import Tooltip from "@mui/material/Tooltip"; + +interface CoreSwitcherProps { + checked: boolean; + onChange: (event: React.ChangeEvent) => void; +} + +export default function CoreSwitcher({ checked, onChange }: CoreSwitcherProps) { + return ( + <> + + + } + label="Use Multi-thread" + disabled={typeof SharedArrayBuffer !== "function"} + /> + + + + + + + + + ); +} diff --git a/apps/website/src/components/Playground/Editor.tsx b/apps/website/src/components/Playground/Editor.tsx index b872ef5..7fb059e 100644 --- a/apps/website/src/components/Playground/Editor.tsx +++ b/apps/website/src/components/Playground/Editor.tsx @@ -12,29 +12,47 @@ import ListItem from "@mui/material/ListItem"; import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemText from "@mui/material/ListItemText"; import IconButton from "@mui/material/IconButton"; -import MoreVertIcon from "@mui/icons-material/MoreVert"; import RefreshIcon from "@mui/icons-material/Refresh"; import UploadFileIcon from "@mui/icons-material/UploadFile"; import UploadIcon from "@mui/icons-material/Upload"; +import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile"; +import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder"; import Tooltip from "@mui/material/Tooltip"; -import Menu from "@mui/material/Menu"; -import MenuItem from "@mui/material/MenuItem"; +import Paper from "@mui/material/Paper"; import { useColorMode } from "@docusaurus/theme-common"; import { FFmpeg } from "@ffmpeg/ffmpeg"; import { fetchFile } from "@ffmpeg/util"; +import { downloadFile } from "@site/src/util"; import AceEditor from "react-ace"; -import styles from "./styles.module.css"; import { getFFmpeg } from "./ffmpeg"; import { SAMPLE_FILES } from "./const"; import LinearProgressWithLabel from "./LinearProgressWithLabel"; +import MoreButton from "./MoreButton"; import "ace-builds/src-noconflict/mode-json"; import "ace-builds/src-noconflict/mode-javascript"; import "ace-builds/src-noconflict/mode-text"; import "ace-builds/src-noconflict/theme-dracula"; import "ace-builds/src-noconflict/theme-github"; +import ListItemButton from "@mui/material/ListItemButton"; +import Modal from "@mui/material/Modal"; +import TextField from "@mui/material/TextField"; const defaultArgs = JSON.stringify(["-i", "video.avi", "video.mp4"], null, 2); -const options = ["Download", "Download as Text File", "Delete"]; +const options = [ + { text: "Download", key: "download" }, + { text: "Download as Text File", key: "download-text" }, + { text: "Delete", key: "delete" }, +]; + +const modalStyle = { + position: "absolute" as "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: 400, + bgcolor: "background.paper", + p: 4, +}; const genFFmpegText = (args: string) => { let data: any = []; @@ -54,33 +72,42 @@ export default function Editor() { const [logs, setLogs] = useState([]); const [output, setOutput] = useState(); const [path, setPath] = useState("/"); - const [nodes, setNodes] = useState([]); - const [progress, setProgress] = useState(-1); + const [nodes, setNodes] = useState([]); + const [progress, setProgress] = useState(0); + const [open, setOpen] = useState(false); + const [time, setTime] = useState(0); + const [folderName, setFolderName] = useState(""); + const handleModalOpen = () => setOpen(true); + const handleModalClose = () => setOpen(false); const { colorMode } = useColorMode(); + const theme = colorMode === "dark" ? "github" : "dracula"; const scrollToEnd = () => { output && output.renderer.scrollToLine(Number.POSITIVE_INFINITY); }; - const loadSamples = async () => { + const refreshDir = async (curPath: string) => { const ffmpeg = getFFmpeg(); - Object.keys(SAMPLE_FILES).forEach(async (name) => { - await ffmpeg.writeFile(name, await fetchFile(SAMPLE_FILES[name])); - }); - // Somehow we need to wait a little bit before reading the new nodes. - setTimeout(async () => { - setNodes(await ffmpeg.listDir(path)); - }, 500); + if (ffmpeg.loaded) { + setNodes( + (await ffmpeg.listDir(curPath)).filter(({ name }) => name !== ".") + ); + } }; - const refreshDir = async () => { - setNodes(await getFFmpeg().listDir(path)); + const loadSamples = async () => { + const ffmpeg = getFFmpeg(); + for (const name of Object.keys(SAMPLE_FILES)) { + await ffmpeg.writeFile(name, await fetchFile(SAMPLE_FILES[name])); + } + refreshDir(path); }; const exec = async () => { const ffmpeg = getFFmpeg(); - setProgress(-1); + setProgress(0); + setTime(0); const logListener = ({ message }) => { setLogs((_logs) => [..._logs, message]); scrollToEnd(); @@ -90,157 +117,271 @@ export default function Editor() { }; ffmpeg.on(FFmpeg.LOG, logListener); ffmpeg.on(FFmpeg.PROGRESS, progListener); + const start = performance.now(); await ffmpeg.exec(JSON.parse(args)); + setTime(performance.now() - start); ffmpeg.removeListener(FFmpeg.LOG, logListener); ffmpeg.removeListener(FFmpeg.PROGRESS, progListener); - setNodes(await ffmpeg.listDir(path)); + refreshDir(path); + }; + + const cd = (name: string) => async () => { + let nextPath = path; + if (path === "/") { + if (name !== "..") nextPath = `/${name}`; + } else if (name === "..") { + const cols = path.split("/"); + cols.pop(); + nextPath = cols.length === 1 ? "/" : cols.join("/"); + } else { + nextPath = `${path}/${name}`; + } + setPath(nextPath); + refreshDir(nextPath); + }; + + const handleFileUpload = + (isText: boolean = false) => + async ({ target: { files } }: React.ChangeEvent) => { + const ffmpeg = getFFmpeg(); + for (let i = 0; i < files.length; i++) { + const file = files[i]; + let data: Uint8Array | string = await fetchFile(file); + if (isText) data = new TextDecoder().decode(data); + await ffmpeg.writeFile(`${path}/${file.name}`, data); + } + refreshDir(path); + }; + + const handleItemClick = (name: string) => async (option: string) => { + const ffmpeg = getFFmpeg(); + const fullPath = `${path}/${name}`; + switch (option) { + case "download": + downloadFile( + name, + ((await ffmpeg.readFile(fullPath, "binary")) as Uint8Array).buffer + ); + break; + case "download-text": + downloadFile(name, await ffmpeg.readFile(fullPath, "utf8")); + break; + case "delete": + await ffmpeg.deleteFile(fullPath); + refreshDir(path); + break; + default: + break; + } + }; + + const handleFolderCreate = async () => { + if (folderName !== "") { + await getFFmpeg().createDir(`${path}/${folderName}`); + } + refreshDir(path); + handleModalClose(); }; useEffect(() => { - const ffmpeg = getFFmpeg(); - ffmpeg.listDir(path).then((nodes) => { - setNodes(nodes); - }); + refreshDir(path); }, []); return ( - + - - File System: - - - {}} aria-label="upload-media-file"> - - - - - {}} aria-label="upload-text"> - - - - - - - - - - - {`${path}`} - - {nodes.map((node, index) => ( - - - - - - {options.map((option) => ( - - {option} - - ))} - - - } - > - - - - - - ))} - + + + <> + + File System: + + + + + + + + + {}} + aria-label="upload-text" + component="label" + size="small" + > + + + + + + { + setFolderName(""); + handleModalOpen(); + }} + aria-label="create-a-new-folder" + size="small" + > + + + + + refreshDir(path)} + aria-label="fresh" + size="small" + > + + + + + + {`Path: ${path}`} + + {nodes.map(({ name, isDir }, index) => + isDir ? ( + + + + + + + ) : ( + + } + > + + + + + + ) + )} + + + + + - - - Edit JSON below to update command: + + + + Edit JSON below to update command: + setArgs(value)} + setOptions={{ tabSize: 2 }} + /> + + Console Output: + setArgs(value)} + readOnly + showPrintMargin={true} + highlightActiveLine={false} + value={logs.join("\n")} setOptions={{ tabSize: 2 }} + onLoad={(editor) => setOutput(editor)} /> - - - - - + Transcoding Progress: + + + + {time === 0 + ? "" + : `Time Elapsed: ${(time / 1000).toFixed(2)} s`} + + + - {progress === -1 ? ( - <> - ) : ( - <> - Transcoding Progress: - - - )} - setOutput(editor)} - /> - + + + + + + Folder Name: + + setFolderName(event.target.value)} + /> + + + + + + + ); } diff --git a/apps/website/src/components/Playground/MoreButton.tsx b/apps/website/src/components/Playground/MoreButton.tsx new file mode 100644 index 0000000..2f802c3 --- /dev/null +++ b/apps/website/src/components/Playground/MoreButton.tsx @@ -0,0 +1,69 @@ +import * as React from "react"; +import IconButton from "@mui/material/IconButton"; +import Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; +import MoreVertIcon from "@mui/icons-material/MoreVert"; + +const ITEM_HEIGHT = 48; + +interface Option { + key: string; + text: string; +} + +interface MoreButtonProps { + options: Option[]; + onItemClick: (option: string) => any; +} + +export default function MoreButton({ options, onItemClick }: MoreButtonProps) { + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + const handleItemClick = (option: string) => () => { + onItemClick(option); + setAnchorEl(null); + }; + + return ( +
+ + + + + {options.map(({ text, key }) => ( + + {text} + + ))} + +
+ ); +} diff --git a/apps/website/src/components/Playground/const.ts b/apps/website/src/components/Playground/const.ts index 907dd87..a4f1802 100644 --- a/apps/website/src/components/Playground/const.ts +++ b/apps/website/src/components/Playground/const.ts @@ -1,9 +1,14 @@ +export const CORE_VERSION = "0.12.0-alpha.2"; + +export const CORE_URL = `https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.js`; +export const CORE_MT_URL = `https://unpkg.com/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.js`; + export const CORE_SIZE = { - "https://unpkg.com/@ffmpeg/core@0.12.0-alpha.2/dist/umd/ffmpeg-core.js": 111646, - "https://unpkg.com/@ffmpeg/core@0.12.0-alpha.2/dist/umd/ffmpeg-core.wasm": 31967534, - "https://unpkg.com/@ffmpeg/core-mt@0.12.0-alpha.2/dist/umd/ffmpeg-core.js": 130002, - "https://unpkg.com/@ffmpeg/core-mt@0.12.0-alpha.2/dist/umd/ffmpeg-core.wasm": 32441947, - "https://unpkg.com/@ffmpeg/core-mt@0.12.0-alpha.2/dist/umd/ffmpeg-core.worker.js": 2978, + [`https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.js`]: 111646, + [`https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.wasm`]: 31967534, + [`https://unpkg.com/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.js`]: 130002, + [`https://unpkg.com/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.wasm`]: 32441947, + [`https://unpkg.com/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.worker.js`]: 2978, }; export const SAMPLE_FILES = { diff --git a/apps/website/src/components/Playground/index.tsx b/apps/website/src/components/Playground/index.tsx index dd66d87..82626c5 100644 --- a/apps/website/src/components/Playground/index.tsx +++ b/apps/website/src/components/Playground/index.tsx @@ -1,10 +1,12 @@ import * as React from "react"; import { FFmpeg } from "@ffmpeg/ffmpeg"; +import Stack from "@mui/material/Stack"; import MuiThemeProvider from "@site/src/components/MuiThemeProvider"; -import CoreSelector from "./CoreSelector"; import CoreDownloader from "./CoreDownloader"; import Editor from "./Editor"; import { getFFmpeg } from "./ffmpeg"; +import { CORE_URL, CORE_MT_URL } from "./const"; +import CoreSwitcher from "./CoreSwitcher"; enum State { NOT_LOADED, @@ -13,12 +15,12 @@ enum State { } export default function Playground() { - const { useState } = React; + const { useState, useEffect } = React; const [state, setState] = useState(State.LOADED); - const [option, setOption] = useState("core"); + const [isCoreMT, setIsCoreMT] = useState(false); const [url, setURL] = useState(""); const [received, setReceived] = useState(0); - const load = async () => { + const load = async (mt: boolean = false) => { setState(State.LOADING); const ffmpeg = getFFmpeg(); ffmpeg.terminate(); @@ -26,29 +28,38 @@ export default function Playground() { setURL(_url as string); setReceived(_received); }); - await ffmpeg.load(); + await ffmpeg.load({ + coreURL: mt ? CORE_MT_URL : CORE_URL, + thread: mt, + }); setState(State.LOADED); }; + useEffect(() => { + load(isCoreMT); + }, []); + return ( - {(() => { - switch (state) { - case State.LOADING: - return ; - case State.LOADED: - return ; - default: - return <>; - } - })()} - { - setOption((event.target as HTMLInputElement).value); - }} - onSubmit={load} - /> + + { + setIsCoreMT(evt.target.checked); + load(evt.target.checked); + }} + /> + {(() => { + switch (state) { + case State.LOADING: + return ; + case State.LOADED: + return ; + default: + return <>; + } + })()} + ); } diff --git a/apps/website/src/components/Playground/styles.module.css b/apps/website/src/components/Playground/styles.module.css index b6a7e1e..e69de29 100644 --- a/apps/website/src/components/Playground/styles.module.css +++ b/apps/website/src/components/Playground/styles.module.css @@ -1,13 +0,0 @@ -.margin { - margin-bottom: 32px; -} - -.alignRight { - display: flex; - justify-content: flex-end; -} - -.fsTitle { - align-items: center; - justify-content: space-between; -} diff --git a/apps/website/src/util.ts b/apps/website/src/util.ts new file mode 100644 index 0000000..8a65af5 --- /dev/null +++ b/apps/website/src/util.ts @@ -0,0 +1,9 @@ +export const downloadFile = (name: string, data: ArrayBuffer | string) => { + const a = document.createElement("a"); + const blob = new Blob([data]); + const url = window.URL.createObjectURL(blob); + a.href = url; + a.download = name; + a.click(); + window.URL.revokeObjectURL(url); +}; diff --git a/packages/ffmpeg/src/classes.ts b/packages/ffmpeg/src/classes.ts index d04fe98..e14df3a 100644 --- a/packages/ffmpeg/src/classes.ts +++ b/packages/ffmpeg/src/classes.ts @@ -4,7 +4,7 @@ import { CallbackData, Callbacks, DownloadProgressEvent, - FFFSPaths, + FSNode, FFMessageEventCallback, FFMessageLoadConfig, OK, @@ -94,6 +94,8 @@ export class FFmpeg extends EventEmitter { #resolves: Callbacks = {}; #rejects: Callbacks = {}; + public loaded = false; + constructor() { super(); } @@ -108,6 +110,9 @@ export class FFmpeg extends EventEmitter { }: FFMessageEventCallback) => { switch (type) { case FFMessageType.LOAD: + this.loaded = true; + this.#resolves[id](data); + break; case FFMessageType.EXEC: case FFMessageType.WRITE_FILE: case FFMessageType.READ_FILE: @@ -227,6 +232,7 @@ export class FFmpeg extends EventEmitter { if (this.#worker) { this.#worker.terminate(); this.#worker = null; + this.loaded = false; } }; @@ -323,11 +329,11 @@ export class FFmpeg extends EventEmitter { * * @category File System */ - public listDir = (path: string): Promise => + public listDir = (path: string): Promise => this.#send({ type: FFMessageType.LIST_DIR, data: { path }, - }) as Promise; + }) as Promise; /** * Delete an empty directory. diff --git a/packages/ffmpeg/src/types.ts b/packages/ffmpeg/src/types.ts index 66f6927..c2f7449 100644 --- a/packages/ffmpeg/src/types.ts +++ b/packages/ffmpeg/src/types.ts @@ -1,5 +1,4 @@ export type FFFSPath = string; -export type FFFSPaths = FFFSPath[]; /** * ffmpeg-core loading configuration. @@ -136,6 +135,11 @@ export type FileData = Uint8Array | string; export type IsFirst = boolean; export type OK = boolean; +export interface FSNode { + name: string; + isDir: boolean; +} + export type CallbackData = | FileData | ExitCode @@ -146,7 +150,7 @@ export type CallbackData = | IsFirst | OK | Error - | FFFSPaths + | FSNode[] | undefined; export interface Callbacks { diff --git a/packages/ffmpeg/src/worker.ts b/packages/ffmpeg/src/worker.ts index 1f38e50..14ee56a 100644 --- a/packages/ffmpeg/src/worker.ts +++ b/packages/ffmpeg/src/worker.ts @@ -18,7 +18,7 @@ import type { IsFirst, OK, ExitCode, - FFFSPaths, + FSNode, FileData, } from "./types"; import { toBlobURL } from "./utils"; @@ -118,8 +118,15 @@ const createDir = ({ path }: FFMessageCreateDirData): OK => { return true; }; -const listDir = ({ path }: FFMessageListDirData): FFFSPaths => { - return ffmpeg.FS.readdir(path); +const listDir = ({ path }: FFMessageListDirData): FSNode[] => { + const names = ffmpeg.FS.readdir(path); + const nodes: FSNode[] = []; + for (const name of names) { + const stat = ffmpeg.FS.stat(`${path}/${name}`); + const isDir = ffmpeg.FS.isDir(stat.mode); + nodes.push({ name, isDir }); + } + return nodes; }; // TODO: check if deletion works.