You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
308 lines
9.2 KiB
308 lines
9.2 KiB
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import '../data/services/microsoft_tts_service.dart';
|
|
|
|
/// 微软 TTS 连续语音输出示例页面
|
|
class MicrosoftTtsContinuousExample extends StatefulWidget {
|
|
const MicrosoftTtsContinuousExample({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
State<MicrosoftTtsContinuousExample> createState() => _MicrosoftTtsContinuousExampleState();
|
|
}
|
|
|
|
class _MicrosoftTtsContinuousExampleState extends State<MicrosoftTtsContinuousExample> {
|
|
final MicrosoftTtsService _ttsService = Get.find<MicrosoftTtsService>();
|
|
|
|
// 语音列表
|
|
final List<Map<String, String>> _voices = [
|
|
{'name': '晓晓(女声)', 'value': 'zh-CN-XiaoxiaoNeural'},
|
|
{'name': '云扬(男声)', 'value': 'zh-CN-YunyangNeural'},
|
|
{'name': '晓双(女声)', 'value': 'zh-CN-XiaoshuangNeural'},
|
|
{'name': '云皓(男声)', 'value': 'zh-CN-YunhaoNeural'},
|
|
{'name': '晓墨(女声)', 'value': 'zh-CN-XiaomoNeural'},
|
|
{'name': '云泽(男声)', 'value': 'zh-CN-YunzeNeural'},
|
|
];
|
|
|
|
String _selectedVoice = 'zh-CN-XiaoxiaoNeural';
|
|
double _rate = 0;
|
|
double _pitch = 0;
|
|
bool _isLoading = false;
|
|
String _statusMessage = '';
|
|
|
|
// 预设的连续语音文本
|
|
final List<String> _presetTexts = [
|
|
'欢迎使用微软语音合成服务,这是连续语音输出的第一段文本。',
|
|
'这是第二段文本,用于测试连续语音输出功能。',
|
|
'现在是第三段文本,我们正在测试微软语音合成服务的连续合成能力。',
|
|
'最后一段测试文本,感谢您的收听。',
|
|
];
|
|
|
|
// 自定义文本列表
|
|
final List<TextEditingController> _textControllers = [];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// 初始化文本控制器
|
|
for (final text in _presetTexts) {
|
|
_textControllers.add(TextEditingController(text: text));
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// 释放文本控制器
|
|
for (final controller in _textControllers) {
|
|
controller.dispose();
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
/// 播放连续文本
|
|
Future<void> _speakContinuous() async {
|
|
final texts = _textControllers.map((controller) => controller.text).toList();
|
|
|
|
if (texts.every((text) => text.isEmpty)) {
|
|
_showSnackBar('请至少输入一段文本');
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_isLoading = true;
|
|
_statusMessage = '正在合成语音...';
|
|
});
|
|
|
|
try {
|
|
// 设置语音
|
|
await _ttsService.setVoice(_selectedVoice);
|
|
|
|
// 停止之前的播放
|
|
await _ttsService.stop();
|
|
|
|
// 连续播放多段文本
|
|
final result = await _ttsService.speakMultiple(
|
|
texts.where((text) => text.isNotEmpty).toList(),
|
|
rate: _rate.round(),
|
|
pitch: _pitch.round(),
|
|
);
|
|
|
|
setState(() {
|
|
_statusMessage = result ? '语音合成已加入队列' : '语音合成失败';
|
|
});
|
|
} catch (e) {
|
|
_showSnackBar('语音合成失败: $e');
|
|
} finally {
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
/// 停止播放
|
|
Future<void> _stopSpeaking() async {
|
|
try {
|
|
await _ttsService.stop();
|
|
setState(() {
|
|
_statusMessage = '语音合成已停止';
|
|
});
|
|
} catch (e) {
|
|
_showSnackBar('停止语音合成失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 添加文本输入框
|
|
void _addTextInput() {
|
|
setState(() {
|
|
_textControllers.add(TextEditingController());
|
|
});
|
|
}
|
|
|
|
/// 删除文本输入框
|
|
void _removeTextInput(int index) {
|
|
if (_textControllers.length <= 1) {
|
|
_showSnackBar('至少需要保留一个文本输入框');
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_textControllers[index].dispose();
|
|
_textControllers.removeAt(index);
|
|
});
|
|
}
|
|
|
|
/// 显示提示信息
|
|
void _showSnackBar(String message) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(message)),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('微软连续语音合成示例'),
|
|
),
|
|
body: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: ListView(
|
|
children: [
|
|
// 语音选择
|
|
DropdownButtonFormField<String>(
|
|
value: _selectedVoice,
|
|
decoration: const InputDecoration(
|
|
labelText: '选择语音',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
items: _voices.map((voice) {
|
|
return DropdownMenuItem<String>(
|
|
value: voice['value'],
|
|
child: Text(voice['name']!),
|
|
);
|
|
}).toList(),
|
|
onChanged: (value) {
|
|
if (value != null) {
|
|
setState(() {
|
|
_selectedVoice = value;
|
|
});
|
|
}
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 语速调节
|
|
Row(
|
|
children: [
|
|
const Text('语速:'),
|
|
Expanded(
|
|
child: Slider(
|
|
min: -100,
|
|
max: 100,
|
|
divisions: 20,
|
|
value: _rate,
|
|
label: _rate.round().toString(),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_rate = value;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
Text('${_rate.round()}%'),
|
|
],
|
|
),
|
|
|
|
// 音调调节
|
|
Row(
|
|
children: [
|
|
const Text('音调:'),
|
|
Expanded(
|
|
child: Slider(
|
|
min: -100,
|
|
max: 100,
|
|
divisions: 20,
|
|
value: _pitch,
|
|
label: _pitch.round().toString(),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_pitch = value;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
Text('${_pitch.round()}%'),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// 文本输入列表标题
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text(
|
|
'连续语音文本',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
ElevatedButton.icon(
|
|
onPressed: _addTextInput,
|
|
icon: const Icon(Icons.add),
|
|
label: const Text('添加文本'),
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
// 文本输入列表
|
|
...List.generate(_textControllers.length, (index) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 8.0),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _textControllers[index],
|
|
maxLines: 3,
|
|
decoration: InputDecoration(
|
|
labelText: '文本 ${index + 1}',
|
|
border: const OutlineInputBorder(),
|
|
),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.delete),
|
|
onPressed: () => _removeTextInput(index),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// 操作按钮
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
Expanded(
|
|
child: ElevatedButton.icon(
|
|
onPressed: _isLoading ? null : _speakContinuous,
|
|
icon: const Icon(Icons.play_arrow),
|
|
label: const Text('播放连续语音'),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: ElevatedButton.icon(
|
|
onPressed: _stopSpeaking,
|
|
icon: const Icon(Icons.stop),
|
|
label: const Text('停止'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.red,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// 状态信息
|
|
Obx(() => Text(
|
|
_ttsService.isSpeaking.value
|
|
? '正在播放语音...'
|
|
: _statusMessage,
|
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
|
textAlign: TextAlign.center,
|
|
)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|