书接上文,接入ai,获取ai回复之后,要将ai的回复由文本转语音,需要加入 tts(Text to Speech) 模块。可用的 tts 插件有很多,这里选用最简单的,flutter 自带的 flutter_tts,它使用手机自带的语音模块,使用方便,当然功能也比较基础。

        首先是配置,在 pubspec.yaml 中添加依赖:flutter_tts: ^4.2.5

        其次在 AndroidManifest.xml 中申请权限:

    <queries>
        <intent>
            <action android:name="android.intent.action.TTS_SERVICE"/>
            <data android:mimeType="text/plain"/>
        </intent>
    </queries>

        添加完成后在根目录执行 flutter pub get。

        创建 tts 服务:

lib/services/flutter_tts_service.dart:

// lib/services/tts_service.dart
import 'package:flutter_tts/flutter_tts.dart';

class TtsService {
  // 单例模式(避免重复初始化)
  static final TtsService _instance = TtsService._internal();
  factory TtsService() => _instance;
  TtsService._internal();

  late FlutterTts _tts;

  // 初始化 TTS 引擎
  Future<void> init() async {
    _tts = FlutterTts();

    // 设置中文(关键!)
    await _tts.setLanguage('zh-CN');

    // 可选:设置语速、音量等
    await _tts.setSpeechRate(0.8); // 0.0 ~ 2.0,1.0 为正常
    await _tts.setVolume(1.0); // 0.0 ~ 1.0
    await _tts.setPitch(1.0); // 0.5 ~ 2.0

    // 等待语音播报完成再返回(可选)
    await _tts.awaitSpeakCompletion(true);
  }

  // 播报文本
  Future<void> speak(String text) async {
    if (text.isEmpty) return;

    try {
      await _tts.speak(text);
    } catch (e) {
      print('TTS 播报失败:  $e');
    }
  }

  // 停止播报
  Future<void> stop() async {
    await _tts.stop();
  }
}

主进程中调用:

main.dart:

import 'package:bsapp_clean/services/flutter_tts_service.dart';
import 'package:bsapp_clean/services/qwen_service.dart';
// import 'package:bsapp_clean/services/edge_tts_service.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // TRY THIS: Try running your application with "flutter run". You'll see
        // the application has a purple toolbar. Then, without quitting the app,
        // try changing the seedColor in the colorScheme below to Colors.green
        // and then invoke "hot reload" (save your changes or press the "hot
        // reload" button in a Flutter-supported IDE, or press "r" if you used
        // the command line to start the app).
        //
        // Notice that the counter didn't reset back to zero; the application
        // state is not lost during the reload. To reset the state, use hot
        // restart instead.
        //
        // This works for code too, not just values: Most code changes can be
        // tested with just a hot reload.
        colorScheme: .fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'AI 语音助手'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  // StatefulWidget 表示这是一个有状态组件
  const MyHomePage({
    super.key,
    required this.title,
  }); // 这里的 const 表示该组件不可改变(immutable)

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState(); // 但是可以关联一个可变的状态对象
}

class _MyHomePageState extends State<MyHomePage> {
  final TextEditingController _textController = TextEditingController();
  final List<String> _messages = [];
  late QwenService _aiService;

  @override
  void initState() {
    super.initState();
    _aiService = QwenService('sk-bd415ab7345e431382ea21cd68a0a1d0');
    TtsService().init();
  }

  Future<void> _handleSendMessage() async {
    String msg = _textController.text.trim();
    if (msg.isEmpty) return;

    setState(() {
      _messages.add('👤: $msg');
    });

    // 等待 ai 回复
    String aiReply = await _aiService.sendMessage(msg);
    // 加载 ai 回复
    setState(() {
      _messages.add('🤖  $aiReply');
    });
    // 语音播放 ai 回复
    // EdgeTtsService().speakWithEdgeTTS(aiReply);
    TtsService().speak(aiReply);

    _textController.clear();
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // TRY THIS: Try changing the color here to a specific color (to
        // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
        // change color while the other colors stay the same.
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Column(
        children: [
          // 👆 消息列表区域(可滚动)
          Expanded(
            child: ListView.builder(
              itemCount: _messages.length,
              itemBuilder: (context, index) {
                return Padding(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 16,
                    vertical: 8,
                  ),
                  child: Text(_messages[index], style: TextStyle(fontSize: 16)),
                );
              },
            ),
          ),

          // 输入区域固定在底部
          Container(
            padding: const EdgeInsets.all(16.0),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _textController,
                    decoration: InputDecoration(
                      hintText: 'input message...',
                      border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(8),
                      ),
                    ),
                    onSubmitted: (value) => _handleSendMessage(), // 按回车时触发
                  ),
                ),
                const SizedBox(width: 12),
                // 发送按钮
                ElevatedButton(
                  onPressed: _textController.text.isEmpty
                      ? null
                      : _handleSendMessage, // 点击按钮时触发
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 24,
                      vertical: 16,
                    ),
                  ),
                  child: const Text('Send'),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }
}

        代码修改完成后,执行测试,结果没有声音,我以为是代码问题,代码又查不出来,模拟器中找到闹铃发现也没有声音,这里推荐个大佬的方法:Android Studio 自带 模拟器无声音 解决方法_android 模拟器中禁用snapshot-CSDN博客

        修改后重启项目,发音正常。。。不会在这里插入视频。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐