class HevcToH264Player {
constructor() {
// 关键修正:使用正确的核心库路径(注意文件后缀和目录)
this.config = {
ffmpegScriptUrl: "https://cdnjs.cloudflare.com/ajax/libs/ffmpeg/0.12.15/umd/ffmpeg.min.js",
// 核心库必须使用完整路径,包含wasm文件的正确位置
ffmpegCorePath: "https://cdnjs.cloudflare.com/ajax/libs/ffmpeg-core/0.12.10/esm/ffmpeg-core.js",
};
// DOM元素
this.fileInput = document.getElementById("hevcFileInput");
this.statusEl = document.getElementById("conversionStatus");
this.progressBar = document.getElementById("progressBar");
this.videoContainer = document.getElementById("videoContainer");
// 状态变量
this.ffmpeg = null;
this.isLoaded = false;
this.coreLoaded = false;
// 初始化
this.bindEvents();
this.createVideoElement();
}
// 创建视频元素
createVideoElement() {
this.video = document.createElement("video");
this.video.controls = true;
this.video.style.width = "100%";
this.video.style.display = "none";
this.videoContainer.appendChild(this.video);
this.loadingDiv = document.createElement("div");
this.loadingDiv.style.cssText = `
position: absolute; top:0; left:0; width:100%; height:100%;
background:rgba(0,0,0,0.7); color:white; display:flex;
flex-direction:column; justify-content:center; align-items:center;
`;
this.loadingDiv.innerHTML = '
准备中...
';
this.videoContainer.appendChild(this.loadingDiv);
this.loadingText = this.loadingDiv.querySelector("#loadingText");
// 添加动画样式
const style = document.createElement("style");
style.textContent = `
.spinner { border:4px solid rgba(255,255,255,0.3); border-top:4px solid white;
border-radius:50%; width:40px; height:40px; animation:spin 1s linear infinite; }
@keyframes spin { 0% { transform:rotate(0deg); } 100% { transform:rotate(360deg); } }
`;
document.head.appendChild(style);
}
// 绑定事件
bindEvents() {
this.fileInput.addEventListener("change", (e) => {
if (e.target.files.length) {
this.convert(e.target.files[0]);
}
});
}
// 加载主库
async loadMainLibrary() {
return new Promise((resolve, reject) => {
// if (window.FFmpeg && window.FFmpeg.createFFmpeg) {
// resolve(window.FFmpeg);
// return;
// }
const script = document.createElement("script");
script.src = this.config.ffmpegScriptUrl;
script.type = "text/javascript";
script.onload = () => {
if (window.FFmpeg && window.FFmpeg.createFFmpeg) {
resolve(window.FFmpeg);
} else {
reject(new Error("主库加载但未找到createFFmpeg方法(版本不兼容)"));
}
};
script.onerror = () => reject(new Error("主库加载失败(网络或URL错误)"));
document.head.appendChild(script);
});
}
// 验证核心库是否加载(关键修正)
checkCoreLibraryLoaded() {
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
// 检查核心库的WASM文件是否已加载
const wasmLoaded = document.querySelectorAll(`script[src*="ffmpeg-core.wasm"]`).length > 0;
if (wasmLoaded) {
clearInterval(checkInterval);
this.coreLoaded = true;
resolve();
}
}, 200);
// 核心库加载超时(30秒)
setTimeout(() => {
clearInterval(checkInterval);
resolve(); // 超时也继续,让FFmpeg自己处理错误
}, 30000);
});
}
// 初始化FFmpeg
async initFFmpeg() {
if (this.ffmpeg && this.isLoaded) return;
this.updateStatus("加载主库...");
const FFmpeg = await this.loadMainLibrary();
console.log("FFmpeg", FFmpeg);
this.updateStatus("初始化FFmpeg实例...");
this.ffmpeg = FFmpeg.createFFmpeg({
log: true,
corePath: this.config.ffmpegCorePath,
});
this.updateStatus("加载核心库(约20MB)...");
// 开始加载核心库
const loadPromise = this.ffmpeg.load();
// 等待核心库加载迹象
// await this.checkCoreLibraryLoaded();
// 等待加载完成
await loadPromise;
this.isLoaded = true;
this.updateStatus("FFmpeg完全加载成功");
}
// 更新状态
updateStatus(text) {
if (this.statusEl) this.statusEl.textContent = text;
if (this.loadingText) this.loadingText.textContent = text;
}
// 转码主逻辑
async convert(file) {
this.updateStatus("准备转码...");
this.progressBar.style.width = "0%";
this.loadingDiv.style.display = "flex";
this.video.style.display = "none";
try {
await this.initFFmpeg();
// 写入文件
const inputName = "input.hevc";
const outputName = "output.mp4";
this.ffmpeg.FS("writeFile", inputName, new Uint8Array(await file.arrayBuffer()));
// 转码进度
this.ffmpeg.setProgress(({ ratio }) => {
const progress = Math.round(ratio * 100);
this.progressBar.style.width = `${progress}%`;
this.updateStatus(`转码中:${progress}%`);
});
// 执行转码
this.updateStatus("开始转码...");
await this.ffmpeg.run("-i", inputName, "-c:v", "libx264", "-crf", "23", "-preset", "medium", "-c:a", "aac", "-b:a", "128k", outputName);
// 播放结果
const data = this.ffmpeg.FS("readFile", outputName);
const url = URL.createObjectURL(new Blob([data.buffer], { type: "video/mp4" }));
this.video.src = url;
this.video.onloadedmetadata = () => {
this.video.style.display = "block";
this.loadingDiv.style.display = "none";
this.updateStatus("转码完成,正在播放");
this.video.play().catch(() => {});
};
} catch (error) {
// 核心库未加载的特殊处理
if (!this.coreLoaded) {
error.message = "核心库加载失败,请检查网络或尝试更换CDN";
}
this.updateStatus(`转码失败:${error.message}`);
// console.error("错误详情:", error);
}
}
}
// 页面加载后初始化
document.addEventListener("DOMContentLoaded", () => {
// 确保DOM元素存在
if (["hevcFileInput", "conversionStatus", "progressBar", "videoContainer"].every((id) => document.getElementById(id))) {
window.hevcPlayer = new HevcToH264Player();
} else {
console.error("缺少必要的DOM元素");
}
});