feat(project): init

This commit is contained in:
2026-02-03 18:05:47 +08:00
commit f37a119eff
188 changed files with 29246 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
"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>
)
}