AI对话App-开发-tts语音
·
书接上文,接入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博客
修改后重启项目,发音正常。。。不会在这里插入视频。
更多推荐
所有评论(0)