You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用react-speech-kit时handleEnd函数未调用,无法下载音频

React Speech Kit 无法触发handleEnd及音频下载问题排查与解决

问题分析

1. handleEnd未被调用的可能原因

  • 语音合成未正常执行:若浏览器不支持Web Speech API、当前系统没有可用的语音合成引擎,或隐私设置限制了语音功能,speak调用后会直接失败,不会触发onEnd回调。
  • 回调绑定逻辑问题:react-speech-kit的speak方法对onEnd的封装存在局限,直接传递handleEnd可能无法正确绑定到SpeechSynthesisUtterance的结束事件上。
  • 浏览器兼容性差异:部分移动端浏览器或旧版Chrome对SpeechSynthesis API的事件支持不完善,导致onEnd无法正常触发。

2. 音频下载失败的核心原因

Web Speech API的SpeechSynthesis接口仅负责播放语音,并不提供生成和导出音频文件的能力。你代码中试图通过event.target.blob获取音频数据的方式本身不可行,这是后续无法下载的根本问题。

解决方案

方案1:修复handleEnd触发问题(仅解决回调触发,不解决音频导出)

直接使用原生SpeechSynthesisUtterance绑定onend事件,绕开react-speech-kit的封装局限:

import React, { useState } from 'react';
import './Textarea.css';
import { useSpeechSynthesis } from 'react-speech-kit';

function Textarea(props) {
  const [text, setText] = useState('');
  const [audioURL, setAudioURL] = useState('');
  const { speak } = useSpeechSynthesis();

  const generateAudio = () => {
    if (!window.SpeechSynthesisUtterance) {
      console.log('当前浏览器不支持语音合成');
      return;
    }
    const utterance = new SpeechSynthesisUtterance(text);
    utterance.onend = handleEnd;
    speak(utterance);
    console.log("generateAudio called");
  };

  const handleEnd = () => {
    console.log('handleEnd called');
    // 注意:此处仍无法获取音频blob,仅解决回调触发问题
  };

  return (
    <>
    <div className="text-area-container">
      <label htmlFor={props.id}>{props.label}</label>
      <textarea
        id={props.id}
        name={props.name}
        placeholder="Type or paste here and get audio file"
        rows={props.rows}
        value={text}
        onChange={(event) => setText(event.target.value)}
      />
    </div>
    <button className='convert-btn' onClick={generateAudio}>Generate audio</button>
    </>
  );
}

export default Textarea;

方案2:实现音频下载功能(借助第三方思路)

由于原生API不支持导出音频,推荐两种可行思路:

思路A:使用MediaRecorder录制语音输出(前端方案,兼容性有限)

通过将语音合成的输出路由到MediaRecorder捕获音频流,生成可下载的blob:

import React, { useState, useRef } from 'react';
import './Textarea.css';

function Textarea(props) {
  const [text, setText] = useState('');
  const [audioURL, setAudioURL] = useState('');
  const mediaRecorderRef = useRef(null);
  const audioChunksRef = useRef([]);

  const generateAudio = async () => {
    if (!window.SpeechSynthesisUtterance || !window.MediaRecorder) {
      alert('当前浏览器不支持语音合成或媒体录制');
      return;
    }

    const audioContext = new (window.AudioContext || window.webkitAudioContext)();
    const destination = audioContext.createMediaStreamDestination();
    const mediaRecorder = new MediaRecorder(destination.stream);
    mediaRecorderRef.current = mediaRecorder;
    audioChunksRef.current = [];

    // 监听录制数据
    mediaRecorder.ondataavailable = (e) => {
      e.data.size > 0 && audioChunksRef.current.push(e.data);
    };

    // 录制结束生成下载链接
    mediaRecorder.onstop = () => {
      const blob = new Blob(audioChunksRef.current, { type: 'audio/webm' });
      const url = URL.createObjectURL(blob);
      setAudioURL(url);
    };

    // 创建语音实例并连接到音频上下文
    const utterance = new SpeechSynthesisUtterance(text);
    const synthNode = audioContext.createMediaStreamSource(await navigator.mediaDevices.getUserMedia({ audio: true }));
    synthNode.connect(destination);

    // 启动录制与语音播放
    mediaRecorder.start();
    window.speechSynthesis.speak(utterance);

    // 语音结束后停止录制
    utterance.onend = () => {
      setTimeout(() => {
        mediaRecorder.stop();
        audioContext.close();
      }, 500); // 延迟停止确保捕获完整音频
    };
  };

  const handleDownload = () => {
    if (!audioURL) return;
    const a = document.createElement('a');
    a.href = audioURL;
    a.download = 'audio.webm';
    a.click();
    URL.revokeObjectURL(audioURL);
    setAudioURL('');
  };

  return (
    <>
      <div className="text-area-container">
        <label htmlFor={props.id}>{props.label}</label>
        <textarea
          id={props.id}
          name={props.name}
          placeholder="Type or paste here and get audio file"
          rows={props.rows}
          value={text}
          onChange={(e) => setText(e.target.value)}
        />
      </div>
      <button className='convert-btn' onClick={generateAudio}>Generate audio</button>
      {audioURL && (
        <button className='download-audio-btn' onClick={handleDownload}>
          Download Audio
        </button>
      )}
    </>
  );
}

export default Textarea;

思路B:调用第三方TTS API(稳定可靠方案)

使用百度、阿里云等提供的文本转语音API,直接请求生成音频文件的下载链接,前端只需负责调用API和渲染下载按钮即可。这种方案兼容性更好,且能保证音频质量。

内容的提问来源于stack exchange,提问作者Sagar J

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 12:46:15