我正在使用 Node.js 和 Electron 开发一个应用程序,旨在通过 API 学习英语词汇。然而,我在实现所需功能方面面临挑战。
总体思路如下:我从 API 中检索 10 个单词,并按照用户定义的时间间隔(在本例中为每 5 秒)自动将它们一一显示在窗口中。每个单词都会发送到 AWS Polly 服务以获取其英语发音,然后进行回放。之后,会等待 3 秒,然后播放该英语单词的示例,然后等待指定的时间间隔继续播放下一个单词。
此外,我还添加了一个按钮来重复当前显示的单词及其示例。但是,我正在努力正确实现此功能。当我单击按钮时,下一个单词开始播放,尽管前一个单词仍显示在屏幕上,或者屏幕上的单词发生变化但上一个单词继续播放。
我希望获得有关如何解决此问题的指导,以确保单词和示例播放与用户界面正确同步。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Words in English</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<div id="word"></div>
<div id="error"></div>
<div class="controls">
<input type="number" id="intervalInput" min="1" max="999" value="1" placeholder="Intervalo">
<select id="voiceSelect">
<option value="Matthew">Matthew</option>
<option value="Joanna">Joanna</option>
</select>
<button id="repeatButton">repeat</button>
</div>
</div>
<script src="renderer.js"></script>
</body>
</html>
渲染器.js
const {speak,updateVoiceConfig,pauseAudio,stopAudio,resumeAudio } = require('./polly/pollyServices.js');
let currentIndex = 0;
let intervalId;
let data;
let time =5;
async function fetchData() {
try {
const response = await fetch('http://localhost:3005/randomwords', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error('Error fetching data:', error);
return null;
}
}
function checkFetchApiStatus() {
if (!data) {
document.getElementById('error').innerText = 'No se pudieron obtener los datos de la API.';
return;
}
}
async function loadDataIfNeeded() {
if (currentIndex === 0 || !data || data.length === 0) {
data = await fetchData();
}
}
function displayWord() {
const word = data[currentIndex];
const wordElement = document.getElementById('word');
let content = "";
for (const key in word) {
if (Object.hasOwnProperty.call(word, key) && key !== "wordID") {
content += `${word[key]}\n`;
}
}
wordElement.innerText = content.trim();
}
let isPlaying = false;
async function playWord(text) {
if (isPlaying) {
stopAudio();
}
isPlaying = true;
await speak(text);
isPlaying = false;
}
async function nextWord() {
await loadDataIfNeeded();
checkFetchApiStatus();
await displayWord();
await playWord(data[currentIndex].english);
await new Promise(resolve => setTimeout(resolve, 3000));
await playWord(data[currentIndex].example_sentence);
currentIndex = (currentIndex + 1) % data.length;
intervalId = setTimeout(nextWord, time * 1000);
}
function setupListeners() {
const intervalInput = document.getElementById('intervalInput');
const voiceSelect = document.getElementById('voiceSelect');
intervalInput.addEventListener('input', () => {
time = intervalInput.value;
});
voiceSelect.addEventListener('change', () => {
const selectedVoice = voiceSelect.value;
updateVoiceConfig(selectedVoice);
});
const listenButton = document.getElementById('repeatButton');
listenButton.addEventListener('click', async function () {
});
}
window.onload = function() {
setupListeners();
nextWord();
};
pollyServices.js
const AWS = require('./pollyConfig.js');
const { Howl } = require('howler');
const polly = new AWS.Polly();
let voiceId = 'Matthew'; // Voz predeterminada
function updateVoiceConfig(newVoiceId) {
voiceId = newVoiceId;
}
// Variable global para manejar la instancia actual del sonido
let sound;
async function speak(text) {
try {
const params = {
Text: text,
OutputFormat: 'mp3',
SampleRate: '24000',
VoiceId: voiceId,
TextType: 'ssml',
};
const ssml = `<speak><prosody rate="slow">${text}</prosody><break time="2s"/></speak>`;
params.Text = ssml;
const resultado = await polly.synthesizeSpeech(params).promise();
if (resultado.AudioStream instanceof Buffer) {
// Detiene la reproducción actual si hay una
if (sound) {
sound.stop();
}
// Crea una nueva instancia de Howl para el nuevo audio
sound = new Howl({
src: ['data:audio/mpeg;base64,' + resultado.AudioStream.toString('base64')],
format: ['mp3'],
onplay: () => {
console.log('Audio is playing');
},
onend: () => {
console.log('Audio has finished playing');
}
});
sound.play();
} else {
console.error('Error al sintetizar el habla.');
}
} catch (error) {
console.error('Error al convertir texto a voz:', error);
}
}
function pauseAudio() {
if (sound && sound.playing()) {
sound.pause();
}
}
function stopAudio() {
if (sound) {
sound.stop();
}
}
function resumeAudio() {
if (sound && !sound.playing()) {
sound.play();
}
}
module.exports = {speak,updateVoiceConfig,pauseAudio,stopAudio,resumeAudio} ;
尝试的解决方案: 在 Electron 应用程序界面中实现了一个按钮,用于重复当前显示的单词及其示例。
预期结果: 单击该按钮后,应用程序应重复当前单词及其示例,然后等待指定的时间间隔,然后自动转到下一个单词。音频播放和 UI 应保持同步,确保显示和播放正确的单词和示例。
实际结果: 单击该按钮后,即使屏幕上仍显示前一个单词,也会立即开始播放下一个单词。或者,屏幕上显示的单词发生变化,但音频播放继续前一个单词。这种异步行为破坏了应用程序的预期流程,并导致用户感到困惑。
看起来,尽管实现了重复单词的按钮,但音频播放和 UI 显示之间存在同步问题,导致应用程序的行为偏离预期结果。
我确实尝试实现一种基于标志的方法来停止递归函数,然后通过按下按钮重新启动它以重播当前单词。但是,我遇到了同步问题,因为根据单击按钮的时间,索引会增加 1,导致所有内容变得不同步。同时,当按钮函数执行时,递归函数继续运行。我不确定为什么会发生这种情况。我提供的代码片段只是我当前实现的一个简化示例,没有按钮功能,因为我已经用尽了其他选项,并且我不确定还可以尝试什么。