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.
202 lines
5.7 KiB
202 lines
5.7 KiB
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import '../data/services/microsoft_tts_service.dart';
|
|
|
|
/// 微软 TTS 示例页面
|
|
class MicrosoftTtsExample extends StatefulWidget {
|
|
const MicrosoftTtsExample({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
State<MicrosoftTtsExample> createState() => _MicrosoftTtsExampleState();
|
|
}
|
|
|
|
class _MicrosoftTtsExampleState extends State<MicrosoftTtsExample> {
|
|
final TextEditingController _textController = TextEditingController();
|
|
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 = '';
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_textController.text = '欢迎使用微软语音合成服务,这是一个示例文本。';
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_textController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
/// 播放文本
|
|
Future<void> _speakText() async {
|
|
if (_textController.text.isEmpty) {
|
|
_showSnackBar('请输入要合成的文本');
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_isLoading = true;
|
|
_statusMessage = '正在合成语音...';
|
|
});
|
|
|
|
try {
|
|
// 设置语音
|
|
await _ttsService.setVoice(_selectedVoice);
|
|
|
|
// 生成 SSML
|
|
final ssml = _ttsService.generateSsml(
|
|
text: _textController.text,
|
|
rate: _rate.round(),
|
|
pitch: _pitch.round(),
|
|
);
|
|
|
|
// 播放 SSML
|
|
final result = await _ttsService.speakSsml(ssml);
|
|
|
|
setState(() {
|
|
_statusMessage = result;
|
|
});
|
|
} catch (e) {
|
|
_showSnackBar('语音合成失败: $e');
|
|
} finally {
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
/// 显示提示信息
|
|
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: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// 文本输入框
|
|
TextField(
|
|
controller: _textController,
|
|
maxLines: 5,
|
|
decoration: const InputDecoration(
|
|
labelText: '输入要合成的文本',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// 语音选择
|
|
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),
|
|
|
|
// 播放按钮
|
|
ElevatedButton(
|
|
onPressed: _isLoading ? null : _speakText,
|
|
child: _isLoading
|
|
? const CircularProgressIndicator()
|
|
: const Text('播放'),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// 状态信息
|
|
Text(
|
|
_statusMessage,
|
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|