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.
267 lines
10 KiB
267 lines
10 KiB
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import '../controllers/tts_test_controller.dart';
|
|
|
|
class TtsTestView extends GetView<TtsTestController> {
|
|
const TtsTestView({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('火山语音合成测试'),
|
|
centerTitle: true,
|
|
),
|
|
body: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// 语音类型选择
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'语音类型',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Obx(() => DropdownButton<String>(
|
|
isExpanded: true,
|
|
value: controller.selectedVoiceType.value,
|
|
onChanged: (String? newValue) {
|
|
if (newValue != null) {
|
|
controller.selectedVoiceType.value = newValue;
|
|
}
|
|
},
|
|
items: controller.voiceTypes.map<DropdownMenuItem<String>>((String value) {
|
|
// 根据语音类型ID获取友好名称
|
|
String displayName = _getVoiceTypeDisplayName(value);
|
|
return DropdownMenuItem<String>(
|
|
value: value,
|
|
child: Text(displayName),
|
|
);
|
|
}).toList(),
|
|
)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// 文本输入
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'输入要合成的文本',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
TextField(
|
|
controller: controller.textController,
|
|
maxLines: 5,
|
|
decoration: const InputDecoration(
|
|
hintText: '请输入要转换为语音的文本...',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// 播放控制
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'播放控制',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: [
|
|
Obx(() => ElevatedButton.icon(
|
|
onPressed: controller.isPlaying.value || controller.isLoading.value
|
|
? null
|
|
: () => controller.speakText(),
|
|
icon: controller.isLoading.value
|
|
? const SizedBox(
|
|
width: 20,
|
|
height: 20,
|
|
child: CircularProgressIndicator(strokeWidth: 2)
|
|
)
|
|
: const Icon(Icons.play_arrow),
|
|
label: Text(controller.isLoading.value ? '准备中...' : '播放'),
|
|
)),
|
|
ElevatedButton.icon(
|
|
onPressed: controller.isPlaying.value
|
|
? () => controller.stopSpeaking()
|
|
: null,
|
|
icon: const Icon(Icons.stop),
|
|
label: const Text('停止'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.red,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// 状态显示
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'状态',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Obx(() => Text(
|
|
controller.isLoading.value
|
|
? '准备中...'
|
|
: controller.isPlaying.value
|
|
? '正在播放...'
|
|
: '就绪',
|
|
style: TextStyle(
|
|
color: controller.isLoading.value
|
|
? Colors.orange
|
|
: controller.isPlaying.value
|
|
? Colors.green
|
|
: Colors.grey,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
)),
|
|
const SizedBox(height: 8),
|
|
Obx(() => controller.errorMessage.value.isNotEmpty
|
|
? Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.red.shade50,
|
|
borderRadius: BorderRadius.circular(4),
|
|
border: Border.all(color: Colors.red.shade200),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.error_outline, color: Colors.red, size: 16),
|
|
SizedBox(width: 8),
|
|
Text(
|
|
'错误',
|
|
style: TextStyle(
|
|
color: Colors.red,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 4),
|
|
Text(
|
|
controller.errorMessage.value,
|
|
style: TextStyle(color: Colors.red.shade800),
|
|
),
|
|
if (controller.errorMessage.value.contains('授权错误'))
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 8.0),
|
|
child: Text(
|
|
'提示: 请尝试选择基础语音类型,如"zh_male_qingse_common"或"zh_female_qingse_common"',
|
|
style: TextStyle(
|
|
fontStyle: FontStyle.italic,
|
|
color: Colors.red.shade700,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: const SizedBox.shrink()),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// 根据语音类型ID获取友好名称
|
|
String _getVoiceTypeDisplayName(String voiceTypeId) {
|
|
switch (voiceTypeId) {
|
|
// 趣味方言
|
|
case 'zh_female_wanqudashu_moon_bigtts':
|
|
return '湾区大叔 (趣味方言)';
|
|
case 'zh_female_daimengchuanmei_moon_bigtts':
|
|
return '呆萌川妹 (趣味方言)';
|
|
case 'zh_male_guozhoudege_moon_bigtts':
|
|
return '广州德哥 (趣味方言)';
|
|
case 'zh_male_beijingxiaoye_moon_bigtts':
|
|
return '北京小爷 (趣味方言)';
|
|
case 'zh_male_haoyuxiaoge_moon_bigtts':
|
|
return '浩宇小哥 (趣味方言)';
|
|
|
|
// 通用场景
|
|
case 'zh_male_shaonianzixin_moon_bigtts':
|
|
return '少年梓辛/Brayan (通用场景)';
|
|
|
|
// 角色扮演
|
|
case 'zh_female_meilinvyou_moon_bigtts':
|
|
return '魅力女友 (角色扮演)';
|
|
case 'zh_male_shenyeboke_moon_bigtts':
|
|
return '深夜播客 (角色扮演)';
|
|
case 'zh_female_sajiaonvyou_moon_bigtts':
|
|
return '柔美女友 (角色扮演)';
|
|
case 'zh_female_yuanqinvyou_moon_bigtts':
|
|
return '撒娇学妹 (角色扮演)';
|
|
|
|
// 基础语音类型
|
|
case 'zh_male_qingse_common':
|
|
return '基础男声';
|
|
case 'zh_female_qingse_common':
|
|
return '基础女声';
|
|
case 'zh_male_M392_conversation_wvae_bigtts':
|
|
return '高级男声';
|
|
case 'zh_female_F392_conversation_wvae_bigtts':
|
|
return '高级女声';
|
|
default:
|
|
return voiceTypeId;
|
|
}
|
|
}
|
|
}
|