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

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;
}
}
}