Complete Playground v1
This commit is contained in:
parent
e02437421b
commit
f4a27e3491
@ -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)
|
||||
|
||||
___
|
||||
|
||||
|
@ -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<HTMLInputElement>) => any;
|
||||
onSubmit: () => any;
|
||||
}
|
||||
export default function CoreSelector({
|
||||
option,
|
||||
onChange,
|
||||
onSubmit,
|
||||
}: CoreSelectorProps) {
|
||||
return (
|
||||
<Container className={styles.margin}>
|
||||
<Container className={styles.margin}>
|
||||
<FormControl>
|
||||
<FormLabel id="core-selector">Select a Core Option</FormLabel>
|
||||
<RadioGroup
|
||||
aria-labelledby="core-selector"
|
||||
name="core-selector"
|
||||
value={option}
|
||||
onChange={onChange}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="core"
|
||||
control={<Radio />}
|
||||
label="Core (Slower, but stable)"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="core-mt"
|
||||
disabled={typeof SharedArrayBuffer !== "function"}
|
||||
control={<Radio />}
|
||||
label="Core Multi-threaded (Faster, but lower compatibility and unstable)"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</Container>
|
||||
<Container>
|
||||
<Button variant="contained" onClick={onSubmit}>
|
||||
Load
|
||||
</Button>
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
}
|
34
apps/website/src/components/Playground/CoreSwitcher.tsx
Normal file
34
apps/website/src/components/Playground/CoreSwitcher.tsx
Normal file
@ -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<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export default function CoreSwitcher({ checked, onChange }: CoreSwitcherProps) {
|
||||
return (
|
||||
<>
|
||||
<Stack direction="row" justifyContent="flex-end">
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={checked} onChange={onChange} />}
|
||||
label="Use Multi-thread"
|
||||
disabled={typeof SharedArrayBuffer !== "function"}
|
||||
/>
|
||||
</FormGroup>
|
||||
<Tooltip title="Multi-threaded core is faster, but unstable and not supported by all browsers. Click here for more details.">
|
||||
<IconButton aria-label="help" size="small">
|
||||
<HelpIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
@ -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<string[]>([]);
|
||||
const [output, setOutput] = useState<Ace.Editor>();
|
||||
const [path, setPath] = useState<string>("/");
|
||||
const [nodes, setNodes] = useState<string[]>([]);
|
||||
const [progress, setProgress] = useState<number>(-1);
|
||||
const [nodes, setNodes] = useState([]);
|
||||
const [progress, setProgress] = useState<number>(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,93 +117,181 @@ 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<HTMLInputElement>) => {
|
||||
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 (
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Grid
|
||||
container
|
||||
spacing={{ xs: 2, md: 3 }}
|
||||
columns={{ xs: 4, sm: 8, md: 12 }}
|
||||
>
|
||||
<Grid container spacing={{ xs: 1 }} columns={{ xs: 4, md: 12 }}>
|
||||
<Grid item xs={4}>
|
||||
<Stack direction="row" className={styles.fsTitle}>
|
||||
<Paper variant="outlined" style={{ padding: 8, height: "100%" }}>
|
||||
<Stack justifyContent="space-between" style={{ height: "100%" }}>
|
||||
<>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography>File System:</Typography>
|
||||
<Box>
|
||||
<Tooltip title="Upload a media file">
|
||||
<IconButton onClick={() => {}} aria-label="upload-media-file">
|
||||
<UploadFileIcon />
|
||||
<IconButton
|
||||
aria-label="upload-media-file"
|
||||
component="label"
|
||||
size="small"
|
||||
>
|
||||
<input
|
||||
hidden
|
||||
multiple
|
||||
type="file"
|
||||
onChange={handleFileUpload(false)}
|
||||
/>
|
||||
<UploadFileIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Upload a text file">
|
||||
<IconButton onClick={() => {}} aria-label="upload-text">
|
||||
<UploadIcon />
|
||||
<IconButton
|
||||
onClick={() => {}}
|
||||
aria-label="upload-text"
|
||||
component="label"
|
||||
size="small"
|
||||
>
|
||||
<input
|
||||
hidden
|
||||
multiple
|
||||
type="file"
|
||||
onChange={handleFileUpload(true)}
|
||||
/>
|
||||
<UploadIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Create a new folder">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setFolderName("");
|
||||
handleModalOpen();
|
||||
}}
|
||||
aria-label="create-a-new-folder"
|
||||
size="small"
|
||||
>
|
||||
<CreateNewFolderIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Refresh directory">
|
||||
<IconButton onClick={refreshDir} aria-label="fresh">
|
||||
<RefreshIcon />
|
||||
<IconButton
|
||||
onClick={() => refreshDir(path)}
|
||||
aria-label="fresh"
|
||||
size="small"
|
||||
>
|
||||
<RefreshIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Typography>{`${path}`}</Typography>
|
||||
<List dense={true}>
|
||||
{nodes.map((node, index) => (
|
||||
<ListItem
|
||||
key={index}
|
||||
secondaryAction={
|
||||
<>
|
||||
<IconButton
|
||||
aria-label="more"
|
||||
id="long-button"
|
||||
aria-haspopup="true"
|
||||
edge="end"
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="long-menu"
|
||||
MenuListProps={{
|
||||
"aria-labelledby": "long-button",
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
width: "20ch",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<MenuItem key={option} selected={option === "Download"}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Typography>{`Path: ${path}`}</Typography>
|
||||
<List style={{ height: 480, overflowX: "auto" }}>
|
||||
{nodes.map(({ name, isDir }, index) =>
|
||||
isDir ? (
|
||||
<ListItemButton key={index} onClick={cd(name)}>
|
||||
<ListItemIcon>
|
||||
<FolderIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={node} />
|
||||
<ListItemText primary={name} />
|
||||
</ListItemButton>
|
||||
) : (
|
||||
<ListItem
|
||||
key={index}
|
||||
secondaryAction={
|
||||
<MoreButton
|
||||
options={options}
|
||||
onItemClick={handleItemClick(name)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<InsertDriveFileIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={name} />
|
||||
</ListItem>
|
||||
))}
|
||||
)
|
||||
)}
|
||||
</List>
|
||||
</>
|
||||
<Button onClick={loadSamples}>Load Sample Files</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={8}>
|
||||
<Paper variant="outlined" style={{ padding: 8, height: "100%" }}>
|
||||
<Stack spacing={1}>
|
||||
<Box>
|
||||
<Stack>
|
||||
<Typography>Edit JSON below to update command:</Typography>
|
||||
<AceEditor
|
||||
mode="json"
|
||||
@ -193,7 +308,7 @@ export default function Editor() {
|
||||
onChange={(value) => setArgs(value)}
|
||||
setOptions={{ tabSize: 2 }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
<AceEditor
|
||||
mode="javascript"
|
||||
theme={theme}
|
||||
@ -208,22 +323,8 @@ export default function Editor() {
|
||||
value={genFFmpegText(args)}
|
||||
setOptions={{ tabSize: 2 }}
|
||||
/>
|
||||
<Stack direction="row" spacing={2} className={styles.alignRight}>
|
||||
<Button onClick={loadSamples}>Load Sample Files</Button>
|
||||
<Button variant="contained" onClick={exec}>
|
||||
Run
|
||||
</Button>
|
||||
</Stack>
|
||||
{progress === -1 ? (
|
||||
<></>
|
||||
) : (
|
||||
<>
|
||||
<Typography>Transcoding Progress:</Typography>
|
||||
<LinearProgressWithLabel value={progress} />
|
||||
</>
|
||||
)}
|
||||
<Typography>Console Output:</Typography>
|
||||
<AceEditor
|
||||
placeholder="ffmpeg console output"
|
||||
mode="text"
|
||||
theme={theme}
|
||||
name="console"
|
||||
@ -238,9 +339,49 @@ export default function Editor() {
|
||||
setOptions={{ tabSize: 2 }}
|
||||
onLoad={(editor) => setOutput(editor)}
|
||||
/>
|
||||
<Typography>Transcoding Progress:</Typography>
|
||||
<LinearProgressWithLabel value={progress} />
|
||||
<Stack direction="row" spacing={2} justifyContent="space-between">
|
||||
<Typography>
|
||||
{time === 0
|
||||
? ""
|
||||
: `Time Elapsed: ${(time / 1000).toFixed(2)} s`}
|
||||
</Typography>
|
||||
<Button variant="contained" onClick={exec}>
|
||||
Run
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={handleModalClose}
|
||||
aria-labelledby="new-folder-name"
|
||||
aria-describedby="new-folder-name-description"
|
||||
>
|
||||
<Box sx={modalStyle}>
|
||||
<Stack spacing={4}>
|
||||
<Typography id="modal-modal-title" variant="h6" component="h2">
|
||||
Folder Name:
|
||||
</Typography>
|
||||
<TextField
|
||||
id="outlined-basic"
|
||||
label="my-folder"
|
||||
variant="outlined"
|
||||
value={folderName}
|
||||
onChange={(event) => setFolderName(event.target.value)}
|
||||
/>
|
||||
<Stack direction="row" justifyContent="flex-end">
|
||||
<Button onClick={handleModalClose}>Cancel</Button>
|
||||
<Button variant="contained" onClick={handleFolderCreate}>
|
||||
Create
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Modal>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
69
apps/website/src/components/Playground/MoreButton.tsx
Normal file
69
apps/website/src/components/Playground/MoreButton.tsx
Normal file
@ -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 | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleItemClick = (option: string) => () => {
|
||||
onItemClick(option);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<IconButton
|
||||
aria-label="more"
|
||||
id="more-button"
|
||||
aria-controls={open ? "menu" : undefined}
|
||||
aria-expanded={open ? "true" : undefined}
|
||||
aria-haspopup="true"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="menu"
|
||||
MenuListProps={{
|
||||
"aria-labelledby": "more-button",
|
||||
}}
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
PaperProps={{
|
||||
style: {
|
||||
maxHeight: ITEM_HEIGHT * 4.5,
|
||||
width: "20ch",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{options.map(({ text, key }) => (
|
||||
<MenuItem key={key} onClick={handleItemClick(key)}>
|
||||
{text}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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 = {
|
||||
|
@ -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,12 +28,27 @@ 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 (
|
||||
<MuiThemeProvider>
|
||||
<Stack spacing={4}>
|
||||
<CoreSwitcher
|
||||
checked={isCoreMT}
|
||||
onChange={(evt) => {
|
||||
setIsCoreMT(evt.target.checked);
|
||||
load(evt.target.checked);
|
||||
}}
|
||||
/>
|
||||
{(() => {
|
||||
switch (state) {
|
||||
case State.LOADING:
|
||||
@ -42,13 +59,7 @@ export default function Playground() {
|
||||
return <></>;
|
||||
}
|
||||
})()}
|
||||
<CoreSelector
|
||||
option={option}
|
||||
onChange={(event) => {
|
||||
setOption((event.target as HTMLInputElement).value);
|
||||
}}
|
||||
onSubmit={load}
|
||||
/>
|
||||
</Stack>
|
||||
</MuiThemeProvider>
|
||||
);
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
.margin {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.alignRight {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.fsTitle {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
9
apps/website/src/util.ts
Normal file
9
apps/website/src/util.ts
Normal file
@ -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);
|
||||
};
|
@ -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<FFFSPaths> =>
|
||||
public listDir = (path: string): Promise<FSNode[]> =>
|
||||
this.#send({
|
||||
type: FFMessageType.LIST_DIR,
|
||||
data: { path },
|
||||
}) as Promise<FFFSPaths>;
|
||||
}) as Promise<FSNode[]>;
|
||||
|
||||
/**
|
||||
* Delete an empty directory.
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user