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元素"); } });