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

SpeechToTextProvider无法实现语音逐字转录至输入框的问题求助

解决SpeechToTextProvider实时转录到输入框的问题

核心问题在于SpeechToTextProvider没有直接暴露onResult回调,但它基于Provider状态管理,每次识别结果更新时会自动通知监听者。我们可以通过监听Provider的状态变化来实现逐字转录,具体修改如下:

步骤1:调整状态监听逻辑

把原来直接在build里赋值speechProvider的方式,改为用Consumer监听SpeechToTextProvider的状态变化,这样每次识别结果更新都会触发回调处理:

@override
Widget build(BuildContext context) {
  return Consumer<SpeechToTextProvider>(
    builder: (context, speechProvider, child) {
      // 每次状态变化时处理最新识别结果
      _handleSpeechResult(speechProvider);

      if (speechProvider.isNotAvailable) {
        return const Center(
          child: Text('语音识别不可用,无权限或设备不支持'),
        );
      }
      return MicrophoneWidget(
        speechProvider.isListening, 
        startListening, 
        stopListening, 
        key: UniqueKey()
      );
    },
  );
}

步骤2:实现实时结果处理方法

新增_handleSpeechResult方法替代原resultListener,处理识别结果的实时更新:

void _handleSpeechResult(SpeechToTextProvider speechProvider) {
  final result = speechProvider.lastResult;
  if (result == null) return;

  if (result.finalResult) {
    // 最终结果确认,更新历史输入记录
    widget.setLastInputChange(
      (widget.key as ValueKey<dynamic>).value, 
      widget.controller.text
    );
  } else {
    // 实时拼接输入框内容
    widget.controller.text = '${widget.lastInputChange} ${result.recognizedWords}';
  }
}

步骤3:修正startListening方法

去掉无效的onResult参数,开启partialResults以获取实时中间结果:

void startListening() {
  if (speechProvider.isAvailable && speechProvider.isNotListening) {
    speechProvider.listen(
      partialResults: true, // 必须开启才能拿到实时中间识别结果
    );
  }
}

完整修改后的SpeechProvider代码

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:speech_to_text/speech_to_text_provider.dart';

class SpeechProvider extends StatefulWidget {
  const SpeechProvider({
    super.key,
    required this.controller,
    required this.lastInputChange,
    required this.setLastInputChange,
  });

  final TextEditingController controller;
  final String lastInputChange;
  final void Function(String, String) setLastInputChange;

  @override
  SpeechProviderState createState() => SpeechProviderState();
}

class SpeechProviderState extends State<SpeechProvider> {
  late SpeechToTextProvider speechProvider;

  void startListening() {
    if (speechProvider.isAvailable && speechProvider.isNotListening) {
      speechProvider.listen(
        partialResults: true,
      );
    }
  }

  void stopListening() {
    if (speechProvider.isListening) {
      speechProvider.stop();
      widget.setLastInputChange(
        (widget.key as ValueKey<dynamic>).value, 
        widget.controller.text
      );
    }
  }

  void _handleSpeechResult(SpeechToTextProvider speechProvider) {
    final result = speechProvider.lastResult;
    if (result == null) return;

    if (result.finalResult) {
      widget.setLastInputChange(
        (widget.key as ValueKey<dynamic>).value, 
        widget.controller.text
      );
    } else {
      widget.controller.text = '${widget.lastInputChange} ${result.recognizedWords}';
    }
  }

  @override
  Widget build(BuildContext context) {
    return Consumer<SpeechToTextProvider>(
      builder: (context, provider, child) {
        speechProvider = provider;
        _handleSpeechResult(provider);
        
        if (speechProvider.isNotAvailable) {
          return const Center(
            child: Text('语音识别不可用,无权限或设备不支持'),
          );
        }
        return MicrophoneWidget(
          speechProvider.isListening, 
          startListening, 
          stopListening, 
          key: UniqueKey()
        );
      },
    );
  }
}

class MicrophoneWidget extends StatelessWidget {
  const MicrophoneWidget(this.isListening, this.startListening, this.stopListening, {super.key});

  final bool isListening;
  final void Function() startListening;
  final void Function() stopListening;

  @override
  Widget build(BuildContext context) {
    return TapRegion(
      onTapOutside: (tap) => isListening ? stopListening() : null,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: SizedBox(
          child: FloatingActionButton(
            onPressed: isListening ? stopListening : startListening,
            shape: const RoundedRectangleBorder(
              borderRadius: BorderRadius.all(Radius.circular(80.0)),
            ),
            child: Icon(isListening ? Icons.mic : Icons.mic_off),
          ),
        ),
      ),
    );
  }
}

关键说明

  • partialResults: true是核心:默认关闭状态下只会返回最终识别结果,开启后才能获取实时的中间转录内容。
  • 利用Consumer监听状态:SpeechToTextProvider内部每次更新识别结果时会调用notifyListeners(),Consumer会自动触发回调,实现输入框的实时更新。
  • 状态同步:通过Consumer获取最新的provider实例,确保每次状态变化都能拿到最新的lastResult数据。

内容的提问来源于stack exchange,提问作者Giulia Santoiemma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 12:45:05