SoundsAmerican Word Player

Automatically plays words on SoundsAmerican website with play/stop controls

Size

9.8 KB

Version

1.1.3

Created

Dec 1, 2025

Updated

12 days ago

1// ==UserScript==
2// @name		SoundsAmerican Word Player
3// @description		Automatically plays words on SoundsAmerican website with play/stop controls
4// @version		1.1.3
5// @match		https://*.soundsamerican.net/*
6// @icon		https://robomonkey.io/icon.png?adc3438f5fbb5315
7// ==/UserScript==
8(function() {
9    'use strict';
10    
11    console.log('🎵 SoundsAmerican Word Player 已启动');
12    
13    // 延迟检测容器,确保异步渲染的内容被检测到
14    function waitForContent(callback) {
15        const interval = setInterval(() => {
16            // 检测单词容器
17            const wordContainer = document.querySelector(".sa-most-common-words-container");
18            // 检测音标容器
19            const ipaContainer = document.querySelector("#phtbPhonemesContainer0");
20            
21            if (wordContainer || ipaContainer) {
22                clearInterval(interval);
23                callback({ wordContainer, ipaContainer });
24            }
25        }, 500); // 每 0.5 秒检测一次
26    }
27
28    waitForContent(({ wordContainer, ipaContainer }) => {
29        console.log("✅ 内容已检测到,开始注入按钮");
30        console.log("单词容器:", wordContainer ? "存在" : "不存在");
31        console.log("音标容器:", ipaContainer ? "存在" : "不存在");
32
33        // === 创建按钮容器 ===
34        const containerDiv = document.createElement("div");
35        Object.assign(containerDiv.style, {
36            position: "fixed",
37            right: "20px",
38            bottom: "20px",
39            zIndex: "99999",
40            display: "flex",
41            gap: "10px",
42            flexDirection: "column",
43            alignItems: "flex-end"
44        });
45
46        const playBtn = document.createElement("button");
47        const pauseBtn = document.createElement("button");
48
49        const baseStyle = {
50            color: "#fff",
51            border: "none",
52            padding: "10px 16px",
53            borderRadius: "8px",
54            cursor: "pointer",
55            fontSize: "16px",
56            boxShadow: "0 2px 6px rgba(0,0,0,0.3)"
57        };
58
59        Object.assign(playBtn.style, baseStyle, { background: "#007bff" });
60        Object.assign(pauseBtn.style, baseStyle, { background: "#dc3545", display: "none" });
61
62        playBtn.textContent = "▶ 播放";
63        pauseBtn.textContent = "⏸ 暂停播放";
64
65        containerDiv.appendChild(pauseBtn);
66        containerDiv.appendChild(playBtn);
67        document.body.appendChild(containerDiv);
68
69        console.log("✅ 播放按钮已添加到页面");
70
71        // === 播放控制变量 ===
72        let isPlaying = false;
73        let isPaused = false;
74        let audio = null;
75        let currentIndex = 0;
76        let currentRepeat = 0;
77        let playList = [];
78        let playMode = null; // 'words' 或 'ipa'
79
80        // === 播放逻辑 ===
81        function startPlayback() {
82            // 如果是暂停后继续
83            if (isPaused) {
84                isPaused = false;
85                pauseBtn.textContent = "⏸ 暂停播放";
86                if (playMode === 'ipa') {
87                    // 音标模式:继续点击
88                    continueIpaPlayback();
89                } else {
90                    // 单词模式:继续播放音频
91                    if (audio) {
92                        audio.play();
93                        console.log("▶️ 音频继续播放");
94                    }
95                }
96                return;
97            }
98
99            // ------- 首次播放:判断播放模式 -------
100            // 优先检测音标按钮
101            const ipaButtons = document.querySelectorAll("#phtbPhonemesContainer0 button.ipa-chart-l__phoneme");
102            const wordItems = document.querySelectorAll(".sa-tooltip-indicator[data-audio-id]");
103
104            if (ipaButtons.length > 0) {
105                // 音标模式
106                playMode = 'ipa';
107                startIpaPlayback(ipaButtons);
108            } else if (wordItems.length > 0) {
109                // 单词模式
110                playMode = 'words';
111                startWordPlayback(wordItems);
112            } else {
113                alert("⚠️ 未找到可播放的内容,请确认页面已加载完毕。");
114                console.error("❌ 未找到音标或单词元素");
115                return;
116            }
117        }
118
119        // === 音标播放模式 ===
120        function startIpaPlayback(ipaButtons) {
121            console.log(`🎵 找到 ${ipaButtons.length} 个音标,开始播放(每个点击3次)`);
122            playBtn.disabled = true;
123            playBtn.textContent = "🎧 播放中…";
124            pauseBtn.style.display = "block";
125            isPlaying = true;
126
127            playList = Array.from(ipaButtons).filter(btn => btn.textContent.trim() !== ''); // 过滤空按钮
128            currentIndex = 0;
129            currentRepeat = 0;
130
131            playNextIpa();
132        }
133
134        function playNextIpa() {
135            if (isPaused) {
136                console.log("⏸ 播放已暂停");
137                return;
138            }
139
140            if (currentIndex >= playList.length) {
141                playBtn.textContent = "✅ 播放完成";
142                playBtn.disabled = false;
143                pauseBtn.style.display = "none";
144                isPlaying = false;
145                console.log("✅ 所有音标播放完成");
146                return;
147            }
148
149            const button = playList[currentIndex];
150            const ipaText = button.textContent.trim();
151            console.log(`▶️ 点击第 ${currentIndex + 1}/${playList.length} 个音标:${ipaText}(第 ${currentRepeat + 1} 次)`);
152
153            // 点击按钮
154            button.click();
155
156            currentRepeat++;
157            if (currentRepeat < 3) {
158                // 继续点击同一个音标
159                setTimeout(playNextIpa, 1000); // 每次点击间隔1秒
160            } else {
161                // 下一个音标
162                currentRepeat = 0;
163                currentIndex++;
164                setTimeout(playNextIpa, 1500); // 音标之间停顿1.5秒
165            }
166        }
167
168        function continueIpaPlayback() {
169            console.log("▶️ 继续播放音标");
170            playNextIpa();
171        }
172
173        // === 单词播放模式 ===
174        function startWordPlayback(wordItems) {
175            console.log(`🎵 找到 ${wordItems.length} 个单词,开始播放`);
176            playBtn.disabled = true;
177            playBtn.textContent = "🎧 播放中…";
178            pauseBtn.style.display = "block";
179            isPlaying = true;
180
181            playList = Array.from(wordItems).map(el => ({
182                id: el.getAttribute("data-audio-id"),
183                el
184            }));
185
186            currentIndex = 0;
187            currentRepeat = 0;
188            audio = new Audio();
189
190            const triggerClick = el => {
191                const evt = new MouseEvent("click", { bubbles: true, cancelable: true, view: window });
192                el.dispatchEvent(evt);
193            };
194
195            const hideTooltips = () => {
196                document.querySelectorAll(".sa-tooltip.active").forEach(t => t.classList.remove("active"));
197            };
198
199            const playNext = () => {
200                if (isPaused) return;
201
202                if (currentIndex >= playList.length) {
203                    hideTooltips();
204                    playBtn.textContent = "✅ 播放完成";
205                    playBtn.disabled = false;
206                    pauseBtn.style.display = "none";
207                    isPlaying = false;
208                    console.log("✅ 所有单词播放完成");
209                    return;
210                }
211
212                const { id, el } = playList[currentIndex];
213                console.log(`▶️ 播放第 ${currentIndex + 1}/${playList.length} 个单词:${id}(第 ${currentRepeat + 1} 遍)`);
214
215                triggerClick(el);
216                audio.src = `/audio/words/${id}.mp3`;
217                audio.play().catch(e => {
218                    console.warn("⚠️ 播放被阻止,请点击页面任意位置以启用音频播放", e);
219                    document.body.addEventListener("click", () => audio.play(), { once: true });
220                });
221            };
222
223            audio.addEventListener("ended", () => {
224                if (isPaused) return;
225
226                currentRepeat++;
227                if (currentRepeat < 2) {
228                    playNext();
229                } else {
230                    currentRepeat = 0;
231                    currentIndex++;
232                    setTimeout(playNext, 800);
233                }
234            });
235
236            playNext();
237        }
238
239        // === 暂停 / 继续播放 ===
240        function togglePause() {
241            if (!isPlaying && !isPaused) return;
242
243            if (!isPaused) {
244                // --- 暂停 ---
245                isPaused = true;
246                pauseBtn.textContent = "▶ 继续播放";
247                console.log("⏸ 播放已暂停");
248                if (playMode === 'words' && audio) {
249                    audio.pause();
250                }
251            } else {
252                // --- 继续 ---
253                isPaused = false;
254                pauseBtn.textContent = "⏸ 暂停播放";
255                console.log("▶️ 播放继续");
256                if (playMode === 'words' && audio) {
257                    audio.play();
258                } else if (playMode === 'ipa') {
259                    continueIpaPlayback();
260                }
261            }
262        }
263
264        // === 事件绑定 ===
265        playBtn.addEventListener("click", startPlayback);
266        pauseBtn.addEventListener("click", togglePause);
267
268        // === 按钮 hover 效果 ===
269        playBtn.onmouseenter = () => (playBtn.style.background = "#0056b3");
270        playBtn.onmouseleave = () => (playBtn.style.background = "#007bff");
271        pauseBtn.onmouseenter = () => (pauseBtn.style.background = "#b02a37");
272        pauseBtn.onmouseleave = () => (pauseBtn.style.background = "#dc3545");
273    });
274})();
SoundsAmerican Word Player | Robomonkey