英语听力精听助手 · 字幕拖拽播放器(精讲版)
Overview
本技能产出一个单文件 HTML 听力精听课件的播放器:
- 左栏:播放控制 + 同步字幕(中间准星,拖拽实时 seek)
- 右栏:听力题目与答案解析(逐题展开),支持「跳转到第 N 段」
- 底部:全屏画笔批注(颜色/粗细/橡皮/撤销/清空/退出)
原生 HTML5 Audio + 原生 JS,不引入任何第三方库,双击即可离线运行。
何时使用
- 做英语听力精听 / 跟读 / 听写 / 挖空训练网页;
- 有「音频 + SRT + 听力题」,想要一个可拖拽定位、可讲题的单页课件;
- 提到「字幕拖着走」「拖拽歌词 seek」「点字幕跳转」「准星高亮」「AB 复读」「挖空」等;
- 触发口令:启动听力字幕拖拽播放器。
功能清单(实现必须全部具备)
| # | 功能 | 关键实现 |
|---|---|---|
| 1 | 挖空训练 | 单击单词即挖空、再单击即还原(caretRangeFromPoint 取点击点单词 Range);一律逐词成空:blankFragment(text) 用 split(/([A-Za-z0-9'\u2019-]+)/) 切词/非词,每个单词生成一个 <span class="blank word" data-ans>,标点与空格原样保留——不允许做成一条长直线;单位「单词/整句」只决定作用范围(整句=全句逐词成空,点任一空格整句还原);以字幕哈希为键存 localStorage 自动恢复 |
| 2 | AB 复读 | abA/abB 两个时间点,主循环判断越过 B 点时 seek 回 A;勾选「循环」持续,未勾选则暂停;进度条上用 #markA/#markB 显示位置;设 A 时自动吸附到当前句起点 |
| 3 | 段落跳转 | sections=[{title,start}],来源①题目文件 @ 段名 \| 时间 ②每题 【时间】 ③「按字幕自动切段」每 N 句 ④「当前位置设为段首」;gotoSec() 跳转;可选「段末暂停」 |
| 4 | 双栏界面 | #workspace{display:flex},左 .left 自适应、右 .right 固定 420px;右栏题目默认折叠答案,查看答案/查看解析 才展开,播放到对应位置时卡片自动 .active 高亮 |
| 5 | 画笔 | 全屏 <canvas id="board"> 固定定位 pointer-events:none,body.brushing 时改为 auto;工具:色板、粗细 slider、橡皮独立调粗细 + 圆形光标(悬停「橡皮」按钮上方弹 4–120px 拖动条;启用后 #eraserRing 跟随鼠标显示等径圆环、cursor:none,applyBrush() 用 eraser ? eraserSize : brushSize)、撤销(ImageData 栈,上限 25)、清空、退出(ESC) |
| 6 | 字号调节 | 原文:顶部滑杆 + A-/A+,改 :root{--cue-fs},范围 10–100;题目:右栏滑杆改 :root{--q-fs}(无单位,供 calc(var(--q-fs) * 0.95px)),范围 10–100;两者都存 localStorage |
| 7 | 播放/快进/后退 | ⏮上一句 ⏪-5s ▶/❚❚ ⏩+5s ⏭下一句;键盘空格、←→、↑↓ |
| 8 | 语速调节 | audio.playbackRate 0.5~2.0 七档下拉 |
| 9 | 图片/PDF 原题 | PDF 用浏览器原生 iframe 渲染(URL.createObjectURL);图片显示原图,若配置了 OpenAI 兼容视觉模型(localStorage.vision_cfg)则自动识别成结构化题目并载入右栏 |
| 10 | 手势并行 | 方向定性:mousedown 不 preventDefault(否则浏览器无法开始选词),首次移动超 4px 时按 |dy| >= |dx| ? 'seek' : 'select' 定性;seek → 清掉刚起头的选区并进入拖播;select → 交给浏览器选词,mouseup 时 autoBlankFromSelection() 直接挖空(跨句 / 覆盖整句 80% 词 → 整句,否则 → 短语);未移动则 handleCueClick:命中单词→挖空/还原,点空白→跳句首。Alt+拖动 强制 select |
| 18 | 准星行为 | #crosshair 默认 display:none,仅拖播时加 .show;两侧 #chLeft/#chRight 实时显示目标时间(fmtTenth = mm:ss.d);拖动中静止 IDLE_MS(320ms) 即 flushSeek(true) + play(),松手走 endScrub() 同样对齐并播放——即"停在准星处就从准星开始播放";页面不再出现"准星"二字 |
| 12 | 触屏拖播 | 相对增量式、可无限拖:touchstart/touchmove/touchend(阈值 6px);每次 move 取 dy 累加到 touchTarget(PX_PER_SEC=18,18px≈1s),不依赖手指绝对位置; |
| **方向为「手指向下拖 → 时间后退 → 字幕向下滑;手指向上拖 → 时间前进 → 字幕向上滑」, | ||
即 touchTarget -= dy * boost / PX_PER_SEC(dy 向下为正,故取负号)——这是用户指定的方向,不要改回加号;手指进入上下边缘 70px 内按 boost = 1 + min(8, 越界距离/25) 加速,实现一指拖到底;touchScrub 为真时允许自动滚动(鼠标拖播时必须禁止,否则与绝对映射打架),于是字幕反向同步滑动;touchend 未移动则走点击逻辑,并 preventDefault() 阻止合成鼠标事件 |
||
| 14 | 每题可跳转 | ensureJumpTimes() 四层补齐:①【时间】→精确 ②matchCueByKeywords() 用题干+选项实词(去停用词、≥4 字母)匹配字幕,命中字符数 ≥8 → 关键词 ③所属段落起点 ④按句数均分;来源写入 q.jumpSrc 显示在跳转按钮 title;字幕后加载时 loadSRT() 会重算 |
| 16 | 控制区位置可切 | 播放控制条 + 工具条包在 #funcZone;顶部栏「控制区」下拉选 顶端/底端,用 flex order 切换(body.ctrl-bottom 时 funcZone order 3、stage order 1);顶部加载栏(.topbar,注意是 class 不是 id)也一起下移(order 1↔2),右栏 .rhead 与 #qList 同步换位,存 localStorage ctrl_pos |
| 19 | 无按钮挖空 | 已删除「挖空选中短语」按钮及其 blankSelection();拖选文字后松开即由 autoBlankFromSelection() 自动挖空,不需要任何额外点击 |
| 17 | 自动详解流水线 | 页面「导出解析请求」= buildAnalysisRequest() 导出【原文+题目+答案+任务要求】;命令行用 scripts/prepare_analysis.py;生成规范见 references/auto-analysis.md(四段式:依据/排除/知识点/技巧),结果回填播放器 |
| 15 | 中文独白隐藏 | 解析时 isDirectionLine() 识别中文说明(含 听第/请听/小题/满分/第一节/每段对话…、≥4 个汉字、不以问号结尾、非 【 开头)→ 存入 q.dirs(题前的存 globalDirs 归到下一题);渲染为 .qdir{display:none}(完全不占版面),右栏「中文说明」开关切 body.show-dir |
| 13 | 选项竖排 | 选项渲染为 block(.opt{display:block});解析时用 splitInlineOptions() 按 A/B/C/D 标记把挤在一行的选项拆开,题干里内嵌选项也能拆,并去掉题干冗余的 Q1. 前缀 |
| 11 | 题目/答案多格式 | loadMaterialFile(file,'q'\|'a') 按扩展名分发:txt/json 直读;docx 用最小 ZIP 读取器 + DecompressionStream 取 word/document.xml 的 <w:t>;pdf 解压 FlateDecode 内容流抽 Tj/TJ;答案用 applyAnswers() 支持「逐题 / 区间 1-5 B C A D B / 纯字母序列」三种写法 |
| 0 | 拖播核心(不可退化) | 见下方实现要点 |
输出流程(触发后严格按此三步)
第一步:简要说明实现方案,确认交互逻辑
用 5~10 行说明并请用户确认:
- 材料入口(按钮 / 拖拽 / 粘贴三种,以及何时改用
make_offline.py内嵌); - 拖播核心:按住左键 → Y 坐标命中字幕 → 句内比例线性映射时间 → 80ms 节流 seek(拖动中就跳,不是松手才跳);
- 双栏分工:左拖播+字幕,右题目解析,底部画笔;
- 挖空与拖播的手势冲突处理:挖空模式下暂时停用手势 seek(用户可接受,已在 UI 注明);
- 段落来源的四种方式与「段末暂停」的含义;
- 询问输出目录、文件名,以及是否需要连同材料一起生成离线内嵌版。
第二步(甲):用户给的是图片或 PDF
按 references/question-from-image.md 执行,不要在这条路上偷懒:
- 图片 → 直接用多模态读取(精度最高);或先
python scripts/ocr_image.py 图.png --out raw.txt
用本机零依赖 OCR取原文,再由 Agent 清洗结构。 - PDF → 直接读取 PDF(工具支持按页读取),同样由 Agent 整理成结构化文本。
- 严格按第 5 条列出的输出格式整理:
@ 段名 \| 时间、## Q1.、A.~D.、【答案】、【解析】、【时间】。 - 逐条走查
question-from-image.md的「七个易错点」(OCR 会把选项挤成一行、多栏串位、答案集中写在末尾等)。 - 产出
q.txt后按第二步(乙)打包,并向用户报告题量、段数、缺【时间】的题。
第二步(乙):输出完整单文件 index.html
- 复制
assets/index.html(约 1100 行,全中文注释)到用户指定目录; - 用户当场提供题目时,按「题目/分段文件格式」(见
references/usage.md第五节)整理成@/##/A./【答案】/【解析】/【时间】结构,再粘贴或写入 txt 一起交付; - 需要「双击就能用」就运行:
python scripts/make_offline.py --audio a.mp3 --srt a.srt -q q.txt --out 课件.html - 必须保留中文注释;交付时调用
present_files打开预览。
第二步(丙):用户要求"自动生成详解 / 讲评"时
按 references/auto-analysis.md 执行,不要凭空编造解析:
- 收集材料:原文 SRT、题目、答案(缺答案就先问,或用
applyAnswers导入的答案文件)。 - 生成任务书:优先引导用户点页面「导出解析请求」;命令行场景用
python scripts/prepare_analysis.py --srt a.srt --q q.txt -a ans.txt --out 解析请求.md
(会自动为每题定位证据句并附前后 2 句原文,实测 5 题全部定位正确)。 - 生成详解:优先调用"听力讲评"类技能(如已安装的
english-listening-assistant,
它专做"定位答案句 + 标注听力原文 + 提炼语言点");未安装时由 Agent 按模板直接生成。 - 每题解析必须四段式:依据(引用原句+时间点)→ 排除(干扰项逐个说明)→ 知识点(词汇短语/语音现象/句型)→ 技巧。
- 结果存成 q.txt,用「加载题目」回填或
make_offline.py打包;最后按文档的质量检查表自检 6 条。
第三步:附带使用教程
输出或引用 references/usage.md 的要点:三种加载方式、八项功能速查表、题目/分段文件格式、SRT 规范与容错范围、常见问题排查。
务必提醒:file:// 页面无法读取同目录文件,双击打开需手动选择或生成内嵌版。
核心实现要点(改代码时不要破坏)
- Y 坐标 → 时间映射(
timeFromClientY):遍历字幕getBoundingClientRect(),命中哪句就用
(clientY-rect.top)/rect.height作句内比例映射到[start,end];间隙取最近句start;超出首/末句钳制。 - 拖动期间绝不自动滚动:否则会改变映射关系形成抖动反馈环;拖拽时只更新高亮,不碰
scrollTop。 - seek 节流:
mousemove只写pendingTime,rAF 主循环flushSeek()执行;拖拽中每
SEEK_THROTTLE_MS(默认 80ms)一次,松手/点击时flushSeek(true)立即精确对齐。 - 自动滚动居中:
el.offsetTop + el.offsetHeight/2 - wrap.clientHeight/2,0.18 缓动,<0.6px 吸附;
列表上下padding:45vh保证首末句能滚到准星。 - 手势互斥:
body.cloze时wrap.mousedown提前 return,字幕区改user-select:text;
画笔开启时 canvas(z-index 60)接管鼠标,底部工具条 z-index 70 保证按钮仍可点。 - 模式防呆(血泪教训,必须有):画笔模式会吞掉鼠标(全屏 canvas),开启时必须显示
顶部黄色提示条#modeBanner(写明"字幕拖播已暂停")并提供「退出(Esc)」按钮;
toggleBrush()/updateModeBanner()联动,ESC 可退出。挖空已与拖播并行,不再弹提示条。 - 悬停弹层不能用 absolute:底部栏是
overflow:auto的滚动容器,橡皮大小弹条用
position:absolute会被裁掉,必须position:fixed+ JS 按按钮getBoundingClientRect()
计算坐标(placeEraserPop(),mouseenter时触发)。 - 浏览器解压的两个坑:docx 用 raw deflate;PDF 的 FlateDecode 是 zlib 封装。
且 Chrome 对流尾多余字节极敏感(多一个\n就Failed to fetch),
因此inflateRaw()必须按「deflate-raw / deflate × 尾部裁剪 0~4 字节」逐档重试。 - 布局不许塌陷(曾经的真 bug):
.topbar/.toolrow/.bottombar必须
flex-wrap:nowrap+overflow-x:auto(隐藏滚动条),#stage必须min-height:160px;
否则窗口偏窄时三行工具条换行会把字幕区挤成 0 高 → 拖播彻底失效(实测 800×600 时 wrap_h=0)。 - 长任务性能:每帧只在文本变化时写 DOM(
updateSecInfo用lastSecInfo比对);查找当前句用二分。
禁止行为
- 禁止「松手才跳转」——拖动过程必须实时 seek;
- 禁止引入任何外部 JS/CSS 库或 CDN 资源;
- 禁止未经询问删除旧功能(拖播、准星、节流是底线);
- 不做逐字高亮(整句高亮即可)。
验证方法(本机没有 CDP / Playwright,用这三招)
- 语法:抽出
<script>内容 →node --check。 - 纯逻辑单测:用正则从模板里抽出
parseSRT/parseQuestions/requestSeek+flushSeek
等纯函数,在 node 里配假audio/ 假performance.now()跑断言(本技能已验证过节流:
10ms 不跳、85ms 跳、松手强制跳)。 - 交互 / 文件解析:把合成鼠标事件(
new MouseEvent('mousedown',{clientX,clientY,button:0}))
派发到真实元素;docx/pdf 用 Python 标准库现造(zipfile 造 docx、手写带 FlateDecode 的 PDF)
后 base64 内嵌进测试页,在页面里new File([...])还原并调用解析函数,
把结果写进页面<pre id="TESTOUT">,再用
msedge --headless=new --disable-gpu --window-size=W,H --virtual-time-budget=6000 --dump-dom file:///...
打印 DOM 后正则取出。注意:该模式下 rAF 不运行(实测 1 秒 0 帧),
所以依赖 rAF 主循环的行为(自动滚动、节流 seek、时间刷新)无法用 dump-dom 验证,只能靠单测 + 静态渲染截图。
Resources
assets/index.html
完整可运行的播放器模板(双栏 + 八项功能,全中文注释)。顶部预留
/* __EMBED_START__ */ ... /* __EMBED_END__ */ 注入区,供离线打包脚本替换。
scripts/make_offline.py
把音频转 data URI、字幕与题目文本写入注入区,产出免选择的离线单文件。
python scripts/make_offline.py --audio lesson1.mp3 --srt lesson1.srt -q questions.txt --out 听力课件.html
(-q 可选;自动兼容 UTF-8/UTF-8-BOM/GBK,自动猜音频 MIME;体积约为音频 1.34 倍。)
scripts/ocr_image.py + scripts/winrt_ocr.ps1
本机零第三方依赖的图片 OCR(调用 Windows 系统自带 Windows.Media.Ocr)。
python scripts/ocr_image.py 题目.png --out raw.txt(支持多图、--lang zh-CN)。
依赖:Windows 10/11 + PowerShell 5.1 + 系统 OCR 语言包(英语默认可用)。
PowerShell 5.1 调 WinRT 异步需要 AsTask 桥接(winrt_ocr.ps1 已封装),不要改写成 .GetAwaiter(),会报 System.__ComObject 错误。
references/question-from-image.md
「图片 / PDF 试题 → 结构化题目」的完整流水线:四条路线、统一输出格式、七个 OCR 清洗易错点、交付收尾命令。
用户给的是图片或 PDF 时必读。
references/usage.md
面向使用者的完整教程:界面分区、三种加载方式、九项功能速查表、题目与分段文件格式规范、
SRT 格式要求与容错范围、常见问题排查表。执行第三步时读取并摘取要点输出。