From 047ddd57f67e68f6c1ccfcde7fe2a1bd430d4675 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RQ919RC\\Pc" <1300399510@qq.com> Date: Fri, 25 Jul 2025 18:49:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0HEVC=E8=BD=ACH.264?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E5=99=A8=E5=92=8C=E6=89=93=E5=AD=97=E6=9C=BA?= =?UTF-8?q?=E6=95=88=E6=9E=9C=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加HEVC转H.264播放器功能,包含文件选择、转码进度显示和视频播放 新增打字机效果展示页面,使用Vue实现逐字显示效果 --- 1.html | 55 ++-- 2.html | 88 ++++++ hevc-to-h264-player.js | 199 +++++++++++++ index.html | 617 ++++++++++++++++++++++++++++++----------- 4 files changed, 778 insertions(+), 181 deletions(-) create mode 100644 2.html create mode 100644 hevc-to-h264-player.js diff --git a/1.html b/1.html index a3996d1..8269a84 100644 --- a/1.html +++ b/1.html @@ -1,26 +1,49 @@ - + - - - EasyWasmPlayer-Demo - + HEVC转H.264播放器 -
-
+
+

HEVC转H.264播放器

+ +
请选择HEVC编码的视频文件
+
+
+
+
- + + + diff --git a/2.html b/2.html new file mode 100644 index 0000000..f70d8ea --- /dev/null +++ b/2.html @@ -0,0 +1,88 @@ + + + + + + + 打字机效果 + + + + + + +
+
+
+
+ {{ item.type }} +
: +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/hevc-to-h264-player.js b/hevc-to-h264-player.js new file mode 100644 index 0000000..04f725c --- /dev/null +++ b/hevc-to-h264-player.js @@ -0,0 +1,199 @@ +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元素"); + } +}); diff --git a/index.html b/index.html index 0b8263f..f8074d9 100644 --- a/index.html +++ b/index.html @@ -2,32 +2,32 @@ - - - 5222 - + 你好,招生官专题 - 寄托天下 + + + + + + +
-
- - + + + + +
-
@@ -37,8 +37,6 @@
- -
@@ -83,18 +81,18 @@
- - + +
-
+
-
-
+
+
-
{{ item.title }}
-
{{ item.lecture_time || '长期答疑' }}
+
{{ item.title }}
+
{{ item.lecture_time || "长期答疑" }}
@@ -104,9 +102,32 @@
- - - +
@@ -148,15 +169,13 @@
@@ -171,7 +190,7 @@ {{ it }}季
- + @@ -181,7 +200,7 @@
-
{{ it }}季1
+
{{ it }}季
@@ -205,7 +224,7 @@
- {{ it.date || '长期答疑'}} + {{ it.date || "长期答疑" }}
了解详情 @@ -229,18 +248,31 @@
{{ item.name }}
{{ item.enname }}
-
+
- + + + + + + + {{ it }}季
-
+
- + + + + + + + + + - 更多 - + 更多
@@ -250,34 +282,31 @@
{{ item }}
- - + +
- +
- + {{ item }}季
- + {{ moreSchoolYMList.includes(moreSchoolPitch) ? `${ moreSchoolPitch }季` : '更多' }} @@ -285,7 +314,7 @@
-
+
{{ it }}季
@@ -294,20 +323,21 @@
- +
- {{ item.title }} +
+
- +
- {{ item.date || '长期答疑'}} + {{ item.date || "长期答疑" }}
了解详情 - +
@@ -318,20 +348,27 @@
- -
+ +
+ +
+ + - - + + + + + +