驗證使用者上傳的影片
學習如何在上傳前驗證使用者影片的瀏覽器相容性,並處理不相容格式
在建構接受使用者影片上傳的應用程式時,你可能想要在上傳之前驗證瀏覽器是否可以播放該影片。
檢查影片相容性
使用 canDecode() 函數來檢查影片是否可以被瀏覽器解碼。
canDecode() 函數會檢查影片是否可以在瀏覽器中播放,以及是否可以載入到 @remotion/media 中的 <Video>。
注意:不同的 Remotion 影片組件具有不同的格式相容性:
<Video>:基於 Mediabunny 的自定義影片標籤,支援最重要的影片格式。<Html5Video>:使用瀏覽器的原生解碼,基於<video>元素。<OffthreadVideo>:在渲染時支援更多格式,在 Player 和 Studio 中預覽時使用<Html5Video>。了解更多支援的媒體格式:影片格式
注意:在
@remotion/media中,H.265 編碼格式的影片在普通瀏覽器中受到支援,但在渲染期間會回退到<OffthreadVideo>。
React 中的驗證範例
注意:這是一個顯示驗證流程的簡化範例。在實際應用程式中,實作方式會根據你的上傳策略(直接上傳、預簽名 URL、分段上傳等)以及儲存影片的位置(S3、GCS、你自己的伺服器等)而有所不同。
import { canDecode } from '@remotion/media';
const upload = async (file: File): Promise<string> => {
// 你的上傳實作
return 'https://example.com/video.mp4';
};
const handleUpload = async (file: File) => {
// 先檢查瀏覽器是否可以解碼此影片
const isCompatible = await canDecode(file);
if (!isCompatible) {
// 通知使用者或拒絕影片
alert('不支援此影片格式。');
return;
}
try {
const url = await upload(file);
console.log('影片上傳成功:', url);
} catch (error) {
console.error('處理影片失敗:', error);
alert('影片上傳失敗');
}
};完整的 React 組件範例
以下是一個完整的帶有驗證功能的上傳組件:
import React, { useCallback, useState } from 'react';
import { Player } from '@remotion/player';
import { MyComposition } from './MyComposition';
// 模擬 canDecode 函數(實際使用 @remotion/media 中的版本)
const canDecode = async (src: string | Blob): Promise<boolean> => {
return new Promise((resolve) => {
const video = document.createElement('video');
video.preload = 'metadata';
const url = src instanceof Blob ? URL.createObjectURL(src) : src;
video.onloadedmetadata = () => {
if (src instanceof Blob) URL.revokeObjectURL(url);
resolve(true);
};
video.onerror = () => {
if (src instanceof Blob) URL.revokeObjectURL(url);
resolve(false);
};
video.src = url;
});
};
const upload = async (file: File): Promise<string> => {
// 你的上傳邏輯
return 'https://example.com/video.mp4';
};
type UploadStatus =
| { type: 'idle' }
| { type: 'validating' }
| { type: 'error'; message: string }
| { type: 'uploading' }
| { type: 'done'; url: string };
export const ValidatedVideoUploader: React.FC = () => {
const [status, setStatus] = useState<UploadStatus>({ type: 'idle' });
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const handleFileSelect = useCallback(
async (event: React.ChangeEvent<HTMLInputElement>) => {
if (!event.target.files?.length) return;
const file = event.target.files[0];
// 步驟 1:驗證影片格式
setStatus({ type: 'validating' });
const isCompatible = await canDecode(file);
if (!isCompatible) {
setStatus({
type: 'error',
message: `不支援的影片格式。請上傳 MP4、WebM 或 MOV 格式的影片。`,
});
return;
}
// 步驟 2:建立本地預覽(樂觀更新)
const blobUrl = URL.createObjectURL(file);
setPreviewUrl(blobUrl);
setStatus({ type: 'uploading' });
try {
// 步驟 3:上傳到雲端
const cloudUrl = await upload(file);
// 步驟 4:切換到雲端 URL
setPreviewUrl(cloudUrl);
setStatus({ type: 'done', url: cloudUrl });
// 釋放 blob URL
URL.revokeObjectURL(blobUrl);
} catch (error) {
setStatus({
type: 'error',
message: '上傳失敗,請重試。',
});
URL.revokeObjectURL(blobUrl);
setPreviewUrl(null);
}
},
[]
);
return (
<div style={{ padding: '20px' }}>
<h2>影片上傳</h2>
{/* 狀態顯示 */}
{status.type === 'validating' && (
<div style={{ color: 'blue' }}>正在驗證影片格式...</div>
)}
{status.type === 'error' && (
<div style={{ color: 'red', padding: '10px', background: '#fee' }}>
{status.message}
</div>
)}
{status.type === 'uploading' && (
<div style={{ color: 'orange' }}>正在上傳影片...</div>
)}
{status.type === 'done' && (
<div style={{ color: 'green' }}>影片上傳成功!</div>
)}
{/* 影片預覽 */}
{previewUrl && (
<div style={{ margin: '20px 0' }}>
<Player
component={MyComposition}
inputProps={{ videoURL: previewUrl }}
durationInFrames={150}
fps={30}
compositionWidth={1920}
compositionHeight={1080}
style={{ width: '100%', maxWidth: '800px' }}
controls
/>
</div>
)}
{/* 檔案輸入 */}
<input
type="file"
accept="video/*"
onChange={handleFileSelect}
disabled={status.type === 'validating' || status.type === 'uploading'}
/>
<p style={{ color: '#666', fontSize: '14px' }}>
支援的格式:MP4、WebM、MOV、AVI
</p>
</div>
);
};處理不相容的影片
當影片無法被解碼時,你有兩個選擇:
選項 1:拒絕影片並要求使用者上傳不同格式
這是最簡單的方法,直接告知使用者格式不受支援:
if (!isCompatible) {
alert('此影片格式不受支援。請嘗試以下格式:MP4 (H.264)、WebM (VP8/VP9)');
return;
}選項 2:在後端重新編碼影片
如果你需要支援各種格式,可以在後端重新編碼影片:
import { execSync } from 'child_process';
import path from 'path';
export const transcodeVideo = async (
inputPath: string,
outputPath: string
): Promise<void> => {
// 使用 FFmpeg 將影片轉換為 H.264/MP4 格式
execSync(
`ffmpeg -i "${inputPath}" -c:v libx264 -c:a aac -movflags +faststart "${outputPath}"`,
{ stdio: 'inherit' }
);
};import { transcodeVideo } from './transcode';
import fs from 'fs';
import path from 'path';
export const handleVideoUpload = async (file: Express.Multer.File) => {
const inputPath = file.path;
const outputPath = path.join('uploads', `${Date.now()}.mp4`);
// 嘗試轉碼
try {
await transcodeVideo(inputPath, outputPath);
// 刪除原始上傳的檔案
fs.unlinkSync(inputPath);
return {
success: true,
url: `/uploads/${path.basename(outputPath)}`,
};
} catch (error) {
fs.unlinkSync(inputPath);
throw new Error('影片轉碼失敗');
}
};支援的影片格式
以下是不同組件的格式支援情況:
| 格式 | <Video> | <Html5Video> | <OffthreadVideo> (渲染) |
|---|---|---|---|
| MP4 (H.264) | 是 | 是 | 是 |
| MP4 (H.265) | 是* | 瀏覽器相依 | 是 |
| WebM (VP8/VP9) | 是 | 是 | 是 |
| WebM (AV1) | 是 | 瀏覽器相依 | 是 |
| MOV | 是 | 瀏覽器相依 | 是 |
| GIF | - | 是 | 是 |
*H.265 在渲染時回退到 <OffthreadVideo>