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

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,
)),
],
),
),
);
}
}