Skip to main content

处理用户视频上传

在一个用户可以上传视频并编辑它们的应用中,我们可以通过在上传完成之前将视频加载到播放器中来提供更好的用户体验。好消息是:这可以很容易地实现!

允许用户上传

我们有一个组件,返回一个包含 URL 作为源的 <Video> 标签。

MyComposition.tsx
tsx
import { AbsoluteFill, Video } from "remotion";
 
type VideoProps = {
videoURL: string;
};
 
export const MyComponent: React.FC<VideoProps> = ({ videoURL }) => {
return (
<AbsoluteFill>
<Video src={videoURL} />
</AbsoluteFill>
);
};
MyComposition.tsx
tsx
import { AbsoluteFill, Video } from "remotion";
 
type VideoProps = {
videoURL: string;
};
 
export const MyComponent: React.FC<VideoProps> = ({ videoURL }) => {
return (
<AbsoluteFill>
<Video src={videoURL} />
</AbsoluteFill>
);
};

视频 URL 将从 Remotion Player 传递给我们的组件。
使用 <input type="file"> 元素,我们允许用户上传。
一旦文件完全上传到云端,URL 将被设置,并且可以被组件用来显示视频。

App.tsx
tsx
import { Player } from "@remotion/player";
import { useState } from "react";
 
export const RemotionPlayer: React.FC = () => {
const [videoUrl, setVideoUrl] = useState<string | null>(null);
 
const handleChange = useCallback(
async (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files === null) {
return;
}
 
const file = event.target.files[0];
//upload is an example function & returns a URL when a file is uploaded on the cloud.
const cloudURL = await upload(file);
// E.g., cloudURL = https://exampleBucketName.s3.ExampleAwsRegion.amazonaws.com
setVideoUrl(cloudURL);
},
[]
);
 
return (
<div>
{videoUrl === null ? null : (
<Player
component={MyComposition}
durationInFrames={120}
compositionWidth={1920}
compositionHeight={1080}
fps={30}
inputProps={{ videoUrl }}
/>
)}
 
<input type="file" onChange={handleChange} />
</div>
);
};
App.tsx
tsx
import { Player } from "@remotion/player";
import { useState } from "react";
 
export const RemotionPlayer: React.FC = () => {
const [videoUrl, setVideoUrl] = useState<string | null>(null);
 
const handleChange = useCallback(
async (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files === null) {
return;
}
 
const file = event.target.files[0];
//upload is an example function & returns a URL when a file is uploaded on the cloud.
const cloudURL = await upload(file);
// E.g., cloudURL = https://exampleBucketName.s3.ExampleAwsRegion.amazonaws.com
setVideoUrl(cloudURL);
},
[]
);
 
return (
<div>
{videoUrl === null ? null : (
<Player
component={MyComposition}
durationInFrames={120}
compositionWidth={1920}
compositionHeight={1080}
fps={30}
inputProps={{ videoUrl }}
/>
)}
 
<input type="file" onChange={handleChange} />
</div>
);
};

upload() 函数的实现是特定于提供程序的,我们在本文中不展示实现。我们假设它是一个接受文件、上传文件并返回 URL 的函数。

乐观更新

当我们开始上传文件时,我们可以使用 URL.createObjectURL() 创建一个 Blob URL,该 URL 可用于在 <Video> 标签中显示本地文件。当文件上传完成并且我们获取到远程 URL 时,组件应使用远程 URL 作为源。

App.tsx
tsx
import { Player } from "@remotion/player";
import { useCallback, useState } from "react";
 
type VideoState =
| {
type: "empty";
}
| {
type: "blob" | "cloud";
url: string;
};
 
export const RemotionPlayer: React.FC = () => {
const [videoState, setVideoState] = useState<VideoState>({
type: "empty",
});
 
const handleChange = useCallback(
async (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files === null) {
return;
}
 
const file = event.target.files[0];
const blobUrl = URL.createObjectURL(file);
setVideoState({ type: "blob", url: blobUrl });
const cloudUrl = await upload(file);
setVideoState({ type: "cloud", url: cloudUrl });
URL.revokeObjectURL(blobUrl);
},
[]
);
 
return (
<div>
{videoState.type !== "empty" ? (
<Player
component={MyComposition}
durationInFrames={120}
compositionWidth={1920}
compositionHeight={1080}
fps={30}
inputProps={{ videoUrl: videoState.url }}
/>
) : null}
<input type="file" onChange={handleChange} />
</div>
);
};
App.tsx
tsx
import { Player } from "@remotion/player";
import { useCallback, useState } from "react";
 
type VideoState =
| {
type: "empty";
}
| {
type: "blob" | "cloud";
url: string;
};
 
export const RemotionPlayer: React.FC = () => {
const [videoState, setVideoState] = useState<VideoState>({
type: "empty",
});
 
const handleChange = useCallback(
async (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files === null) {
return;
}
 
const file = event.target.files[0];
const blobUrl = URL.createObjectURL(file);
setVideoState({ type: "blob", url: blobUrl });
const cloudUrl = await upload(file);
setVideoState({ type: "cloud", url: cloudUrl });
URL.revokeObjectURL(blobUrl);
},
[]
);
 
return (
<div>
{videoState.type !== "empty" ? (
<Player
component={MyComposition}
durationInFrames={120}
compositionWidth={1920}
compositionHeight={1080}
fps={30}
inputProps={{ videoUrl: videoState.url }}
/>
) : null}
<input type="file" onChange={handleChange} />
</div>
);
};

这将导致用户在将视频拖入输入字段时立即看到视频。在不再使用本地视频时,调用 URL.revokeObjectURL() 是一个良好的做法,以释放已使用的内存。

参见