将音频播放与 Node.js 和 Electron App 中的 UI 同步

问题描述 投票:0回答:1

我正在使用 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 显示之间存在同步问题,导致应用程序的行为偏离预期结果。

javascript node.js electron
1个回答
0
投票

我确实尝试实现一种基于标志的方法来停止递归函数,然后通过按下按钮重新启动它以重播当前单词。但是,我遇到了同步问题,因为根据单击按钮的时间,索引会增加 1,导致所有内容变得不同步。同时,当按钮函数执行时,递归函数继续运行。我不确定为什么会发生这种情况。我提供的代码片段只是我当前实现的一个简化示例,没有按钮功能,因为我已经用尽了其他选项,并且我不确定还可以尝试什么。

© www.soinside.com 2019 - 2024. All rights reserved.