125 lines
3.7 KiB
TypeScript
125 lines
3.7 KiB
TypeScript
"use client"
|
|
|
|
import { ChangeEvent, useEffect, useRef, useState } from "react"
|
|
import type { WorkerMessage, WorkerResponse } from "./types/libav"
|
|
|
|
export default function Home() {
|
|
const workerRef = useRef<Worker | null>(null)
|
|
const [ready, setReady] = useState(false)
|
|
const [logs, setLogs] = useState<string[]>([])
|
|
const [videoUrl, setVideoUrl] = useState<string | null>(null)
|
|
const [processing, setProcessing] = useState(false)
|
|
const addLog = (msg: string) => {
|
|
setLogs((prev) => [...prev.slice(-10), msg]) // 只保留最近10条
|
|
}
|
|
useEffect(() => {
|
|
// 初始化 Worker
|
|
// Next.js 会自动检测到这个语法并单独打包 Worker
|
|
workerRef.current = new Worker(
|
|
new URL("./libav.worker.ts", import.meta.url)
|
|
)
|
|
|
|
// 设置消息监听
|
|
workerRef.current.onmessage = (event: MessageEvent<WorkerResponse>) => {
|
|
const { type } = event.data
|
|
|
|
switch (type) {
|
|
case "READY":
|
|
setReady(true)
|
|
addLog("FFmpeg core loaded and ready.")
|
|
break
|
|
case "LOG":
|
|
if ("message" in event.data) addLog(event.data.message)
|
|
break
|
|
case "DONE":
|
|
if ("data" in event.data) {
|
|
const url = URL.createObjectURL(event.data.data)
|
|
setVideoUrl(url)
|
|
setProcessing(false)
|
|
addLog("Transcoding complete!")
|
|
}
|
|
break
|
|
case "ERROR":
|
|
if ("error" in event.data) {
|
|
addLog(`Error: ${event.data.error}`)
|
|
setProcessing(false)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// 告诉 Worker 加载 libav
|
|
// 这里的路径必须指向 public 目录下的实际文件
|
|
// 使用 default-cli 变体以确保包含 MP4/H.264 等常见格式的解封装器和解码器
|
|
const LIBAV_PATH = "/libav/libav-6.8.8.0-default.js"
|
|
workerRef.current.postMessage({
|
|
type: "LOAD",
|
|
config: { corePath: location.origin + LIBAV_PATH },
|
|
} as WorkerMessage)
|
|
|
|
return () => {
|
|
workerRef.current?.terminate()
|
|
}
|
|
}, [])
|
|
|
|
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0]
|
|
if (!file || !workerRef.current || !ready) return
|
|
|
|
setProcessing(true)
|
|
setVideoUrl(null)
|
|
setLogs([])
|
|
addLog(`Starting upload: ${file.name}`)
|
|
|
|
workerRef.current.postMessage({
|
|
type: "TRANSCODE",
|
|
file: file,
|
|
} as WorkerMessage)
|
|
}
|
|
|
|
return (
|
|
<main className="p-8 font-mono">
|
|
<h1 className="text-2xl font-bold mb-4">Next.js + Libav.js + Worker</h1>
|
|
|
|
<div className="mb-4 p-4 border rounded bg-gray-50">
|
|
<p>
|
|
Status:{" "}
|
|
{ready ? (
|
|
<span className="text-green-600">Ready</span>
|
|
) : (
|
|
<span className="text-yellow-600">Loading Core...</span>
|
|
)}
|
|
</p>
|
|
<p>Processing: {processing ? "Yes" : "No"}</p>
|
|
</div>
|
|
|
|
<input
|
|
type="file"
|
|
onChange={handleFileChange}
|
|
disabled={!ready || processing}
|
|
className="mb-4 block w-full text-sm text-slate-500
|
|
file:mr-4 file:py-2 file:px-4
|
|
file:rounded-full file:border-0
|
|
file:text-sm file:font-semibold
|
|
file:bg-violet-50 file:text-violet-700
|
|
hover:file:bg-violet-100"
|
|
/>
|
|
|
|
{videoUrl && (
|
|
<div className="mb-4">
|
|
<h2 className="font-bold">Result:</h2>
|
|
<video controls src={videoUrl} className="w-full max-w-md border" />
|
|
</div>
|
|
)}
|
|
|
|
<div className="bg-black text-white p-4 rounded text-xs h-64 overflow-y-auto">
|
|
{logs.map((log, i) => (
|
|
<div key={i} className="border-b border-gray-800 py-1">
|
|
{log}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</main>
|
|
)
|
|
}
|