200 lines
7.2 KiB
JavaScript
200 lines
7.2 KiB
JavaScript
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 = '<div class="spinner"></div><div id="loadingText">准备中...</div>';
|
||
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元素");
|
||
}
|
||
});
|