Skip to content

Commit be30483

Browse files
committed
feat: update
1 parent a5117d2 commit be30483

File tree

1 file changed

+216
-0
lines changed

1 file changed

+216
-0
lines changed
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
---
2+
layout: post
3+
categories: [杂项] # 文章分类,tags的替代
4+
author: abining
5+
title: 用油猴脚本为 Duome Stories 添加键盘音频控制:从痛点到优化
6+
header-style: text
7+
catalog: true
8+
tags:
9+
- Duome
10+
- 多邻国
11+
- Tampermonkey
12+
- 音频预加载
13+
---
14+
15+
# 🎧 用油猴脚本为 Duome Stories 添加键盘音频控制:从痛点到优化
16+
17+
---
18+
19+
## ✨ 背景:从 Duome 学英语的真实痛点出发
20+
21+
[Duome.eu/stories](https://duome.eu/stories) 是我在学习英语时频繁使用的非官方资源网站,提供了丰富的语音故事。每个句子都配有音频,理论上是沉浸式学习的绝佳方式,但实际体验却有明显瑕疵。
22+
23+
---
24+
25+
## 😫 我的两个核心痛点
26+
27+
### 1. **播放音频延迟太久**
28+
29+
页面中每个句子的音频都是通过浏览器 `GET` 请求以 `206 Partial Content` 方式加载。这意味着音频每次播放都会建立新连接,而 **不会使用缓存(除非之前已经加载)**
30+
31+
* 实测每次点击音频播放,**平均等待约 1000ms**,最慢时可达 **1.5 秒**
32+
33+
![very slow](https://raw.githubusercontent.com/abining/picgo_imgs/main/images20250721191301639.png)
34+
35+
* 页面没有预加载,导致初次体验非常差,通常点击之后要等待一秒钟才播放音频,频繁打断思路。
36+
* 虽然浏览器会缓存 `206` 音频,但仅在 **播放过一次** 后有效。
37+
38+
📌 **我的目标**是:**在页面加载时预先把所有音频下载好**,后续播放直接使用内存中的音频对象,彻底消除初次播放延迟。
39+
40+
---
41+
42+
### 2. **缺乏键盘操作与视觉反馈**
43+
44+
Duome 的页面里布满了几十个音频按钮,全部需要鼠标点击。没有快捷键意味着:
45+
46+
* 无法像播放器一样通过键盘播放/暂停/切换
47+
* 不知道当前播放的是哪一句话,没有视觉提示
48+
49+
📌 我的第二个目标是:**实现快捷键控制 + 播放时高亮当前音频句子**
50+
51+
---
52+
53+
## 🧠 功能设计目标
54+
55+
于是我动手编写了一个 Tampermonkey 用户脚本,目标是实现以下功能:
56+
57+
### ✅ 实现功能(按重要性排序)
58+
59+
***预加载页面中所有音频**,进入页面即可播放,无需等待
60+
***支持快捷键控制播放**(上下句、重播当前)
61+
***高亮当前播放的音频按钮**,方便跟踪
62+
* ✅ 提供图形化设置面板,自定义快捷键
63+
* ✅ 控制面板悬浮右上角,鼠标悬停展开,带动画过渡
64+
65+
---
66+
67+
## 🔍 页面结构分析:Duome 音频标签结构
68+
69+
我们分析页面结构,找到关键播放按钮的 DOM 元素。
70+
71+
```html
72+
<div class="playback voice" data-src="https://.../audio.mp3"></div>
73+
```
74+
75+
观察发现:
76+
77+
* 每个音频按钮是一个 `<div class="playback voice">`
78+
* 音频地址保存在 `data-src` 属性中
79+
* 没有用 `<audio>` 标签(点击按钮后才触发下载)
80+
81+
📌 所以我们可以:
82+
83+
1.`querySelectorAll('.playback.voice')` 抓取所有按钮
84+
2. 读取 `data-src` 并手动创建 `new Audio()` 对象
85+
3. 调用 `.load()` 立即预加载音频
86+
4. 将音频挂载到元素上:`el._audio = audio`
87+
88+
这样就能在页面加载阶段提前准备好所有音频。
89+
90+
---
91+
92+
## 🛠 开发过程与技术选择
93+
94+
### 🎧 音频预加载核心逻辑
95+
96+
```js
97+
function preloadAudios() {
98+
return Array.from(document.querySelectorAll('.playback.voice')).map(el => {
99+
const src = el.dataset.src;
100+
if (src) {
101+
const audio = new Audio(src);
102+
audio.load(); // 主动加载
103+
el._audio = audio; // 挂载到元素
104+
}
105+
return el;
106+
});
107+
}
108+
```
109+
110+
📌 这段代码确保每个按钮都带有一份缓存的 `Audio` 对象,后续播放立即生效。
111+
112+
---
113+
114+
### ⌨️ 快捷键录入方式的取舍
115+
116+
#### 🅰️ 方案一:使用 `<select>` 下拉框选择按键
117+
118+
* ✅ 简单易懂
119+
* ❌ 只能选择固定键,无法支持 `Shift+N` 等组合键
120+
121+
#### 🅱️ 方案二:使用 `keydown` 捕捉用户输入
122+
123+
* ✅ 支持任意按键或组合键
124+
* ✅ 操作更自然(直接按下就记录)
125+
* ❌ 实现略复杂,要处理 `Shift` / `Ctrl` 等修饰符
126+
127+
最终我选择了方式二,用如下方式处理键盘录入:
128+
129+
```js
130+
input.addEventListener('focus', () => {
131+
input.value = '按下键...';
132+
const capture = (e) => {
133+
e.preventDefault();
134+
let key = e.key;
135+
if (e.shiftKey && key !== 'Shift') key = 'Shift+' + key;
136+
input.value = key;
137+
window.removeEventListener('keydown', capture);
138+
};
139+
window.addEventListener('keydown', capture);
140+
});
141+
```
142+
143+
---
144+
145+
### 💾 快捷键配置的存储方式比较
146+
147+
#### ✅ 方式一:`localStorage`
148+
149+
```js
150+
localStorage.setItem('shortcuts', JSON.stringify(config));
151+
```
152+
153+
* ✅ 浏览器原生支持
154+
* ❌ 每个域名独立、数据不隔离,Tampermonkey 脚本难管理
155+
156+
#### ✅ 方式二:Tampermonkey 提供的 `GM_setValue`
157+
158+
```js
159+
GM_setValue('shortcuts', config);
160+
```
161+
162+
* ✅ 配合油猴脚本完美使用,数据独立、跨域共享
163+
* ✅ 可用于任何 Tampermonkey 页面脚本
164+
165+
📌 最终采用 `GM_setValue` / `GM_getValue`,文档参考:[Tampermonkey 文档](https://www.tampermonkey.net/documentation.php#GM_setValue)
166+
167+
---
168+
169+
## 🎛 悬浮控制面板 + 动画展开
170+
171+
为了不打扰页面主体,我把控制面板缩成了右上角一个小齿轮图标。
172+
173+
* 鼠标悬停显示完整控制面板
174+
* 鼠标移出自动收起
175+
* 使用 `opacity``transform` 实现动画过渡
176+
177+
核心 CSS:
178+
179+
```css
180+
#duome-panel {
181+
opacity: 0;
182+
transform: translateY(-10px);
183+
transition: all 0.3s ease;
184+
}
185+
```
186+
187+
---
188+
189+
## 🎯 最终效果
190+
191+
* ✅ 进入页面即自动加载所有音频
192+
* ✅ 按 `Tab` 播放下一句,`Shift+Tab` 播放上一句
193+
* ✅ 按 `r` 重播当前音频
194+
* ✅ 每次播放时自动高亮当前按钮
195+
* ✅ 用户可自定义快捷键
196+
* ✅ 设置面板带动画、悬浮右上角不打扰阅读
197+
198+
![](https://raw.githubusercontent.com/abining/picgo_imgs/main/images20250721192321931.png)
199+
200+
---
201+
202+
## 🔧 后续优化计划
203+
204+
* 加入播放文字提示(显示当前句子)
205+
* 添加“记忆播放位置”功能
206+
* 支持使用 `Esc` 暂停当前音频
207+
* 打卡学习进度(记录每天播放量)
208+
209+
---
210+
211+
## 🧩 结语
212+
213+
这个小脚本极大地提升了我在 Duome 学英语的体验,也让我进一步掌握了用户脚本的开发方式、页面结构解析能力,以及预加载和键盘交互等技巧。
214+
215+
花了小半天的时间写的,也希望能对其他人有作用吧。
216+

0 commit comments

Comments
 (0)