import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:record/record.dart'; import '../../../data/services/speech_factory.dart'; import '../../settings/controllers/settings_controller.dart'; class TimbreController extends GetxController { final SettingsController settingsController = Get.find(); final GetStorage _storage = GetStorage(); final _record = AudioRecorder(); final String _appId = "43d87d22"; final String _appKey = "2bd01ce06ac35957fc0c082f9b6284ec"; RxList dataList = [].obs; RxString type = '1'.obs; RxString text = ''.obs; String _token = ''; int _textId = 0; int _segId = 0; String _taskId = ''; @override void onInit() { super.onInit(); _getDataList(); } @override void onClose() { _record.dispose(); super.onClose(); } void _getDataList() { List? timbreList = _storage.read('timbreList'); String? timbre = _storage.read('timbre'); if (timbreList != null) { dataList.value = timbreList; } else { dataList.add({ 'type': '1', 'name': 'defaultTimbre', 'description': 'microsoftCloudTimbreService', 'isUsable': true }); _storage.write('timbreList', [ { 'type': '1', 'name': 'defaultTimbre', 'description': 'microsoftCloudTimbreService', 'isUsable': true } ]); } if (timbre != null) { type.value = timbre; } } // 录制声音 void startRecorder() async { if (_token.isEmpty) { await _getToken(); } await _getTraintext(); await _addTask(); final directory = await getApplicationDocumentsDirectory(); final path = '${directory.path}/recording_${DateTime.now().millisecondsSinceEpoch}.wav'; await _record.start( const RecordConfig( encoder: AudioEncoder.wav, sampleRate: 24000, numChannels: 1, ), path: path, ); } // 获取token Future _getToken() async { int timeStamp = DateTime.now().millisecondsSinceEpoch; String data = '{"base":{"appid":"$_appId","version":"v1","timestamp":"$timeStamp"},"model":"remote"}'; String keySignInput = _appKey + timeStamp.toString(); List keySignBytes = utf8.encode(keySignInput); Digest keySignDigest = md5.convert(keySignBytes); String keySign = keySignDigest.toString(); String signInput = keySign + data; List signBytes = utf8.encode(signInput); Digest signDigest = md5.convert(signBytes); String sign = signDigest.toString(); final response = await http.post( Uri.parse('http://avatar-hci.xfyousheng.com/aiauth/v1/token'), headers: { 'Content-Type': 'application/json', 'Authorization': sign, }, body: data, ); if (response.statusCode == 200) { final body = json.decode(response.body); if (body['retcode'] == '000000') { _token = body['accesstoken']; } } } // 获取训练文本 Future _getTraintext() async { String data = '{"textId":"5001"}'; Map sign = _sign(data); final response = await http.post( Uri.parse('http://opentrain.xfyousheng.com/voice_train/task/traintext'), headers: { 'Content-Type': 'application/json', 'X-Sign': sign['sign'], 'X-Token': _token, 'X-AppId': _appId, 'X-Time': sign['timeStamp'], }, body: data, ); if (response.statusCode == 200) { final body = json.decode(response.body); if (body['code'] == 0) { _textId = body['data']['textId']; Map textSegs = body['data']['textSegs'].first; _segId = textSegs['segId']; text.value = _decodeCorruptedText(textSegs['segText']); } } } // 创建训练任务 Future _addTask() async { String data = '{"resourceType":"12"}'; Map sign = _sign(data); final response = await http.post( Uri.parse('http://opentrain.xfyousheng.com/voice_train/task/add'), headers: { 'Content-Type': 'application/json', 'X-Sign': sign['sign'], 'X-Token': _token, 'X-AppId': _appId, 'X-Time': sign['timeStamp'], }, body: data, ); if (response.statusCode == 200) { final body = json.decode(response.body); if (body['code'] == 0) { _taskId = body['data']; } } } Map _sign(String data) { int timeStamp = DateTime.now().millisecondsSinceEpoch; String keySignInput = _appKey + timeStamp.toString(); List keySignBytes = utf8.encode(keySignInput); Digest keySignDigest = md5.convert(keySignBytes); String keySign = keySignDigest.toString(); String signInput = keySign + data; List signBytes = utf8.encode(signInput); Digest signDigest = md5.convert(signBytes); String sign = signDigest.toString(); return {'sign': sign, 'timeStamp': timeStamp.toString()}; } String _decodeCorruptedText(String corruptedText) { List latin1Bytes = corruptedText.codeUnits.map((ch) => ch & 0xFF).toList(); return utf8.decode(latin1Bytes); } // 完成录制 void completeRecorder() async { final audioPath = await _record.stop(); String data = '{"taskId":"$_taskId","textId":"$_textId","textSegId":"$_segId"}'; Map sign = _sign(data); final uri = Uri.parse( 'http://opentrain.xfyousheng.com/voice_train/task/submitWithAudio'); final request = http.MultipartRequest('POST', uri); request.files.add(await http.MultipartFile.fromPath( 'file', audioPath!, filename: path.basename(audioPath), )); request.fields.addAll({ 'taskId': _taskId, 'textId': '$_textId', 'textSegId': '$_segId', }); request.headers.addAll({ "X-Sign": sign['sign'], "X-Token": _token, "X-AppId": _appId, "X-Time": sign['timeStamp'], }); final response = await request.send(); if (response.statusCode == 200) { final responseBody = await response.stream.bytesToString(); final body = json.decode(responseBody); if (body['code'] == 0) { text.value = ''; dataList.add({ 'type': _taskId, 'name': '声音复刻${dataList.length}', 'description': '复刻本人音色服务', 'isUsable': false }); await _getTaskResult(_taskId); } } } // 查询训练任务状态 Future _getTaskResult(String id) async { String data = '{"taskId":"$id"}'; Map sign = _sign(data); final response = await http.post( Uri.parse('http://opentrain.xfyousheng.com/voice_train/task/result'), headers: { 'Content-Type': 'application/json', 'X-Sign': sign['sign'], 'X-Token': _token, 'X-AppId': _appId, 'X-Time': sign['timeStamp'], }, body: data, ); if (response.statusCode == 200) { final body = json.decode(response.body); if (body['code'] == 0) { if (body['data']['trainStatus'] == -1) { await _getTaskResult(id); } if (body['data']['trainStatus'] == 1) { Map? dataMap = dataList.firstWhereOrNull((item) => item['type'] == id); if (dataMap != null) { dataMap['type'] = body['data']['assetId']; dataMap['isUsable'] = true; dataList.refresh(); _storage.write('timbreList', dataList); } } if (body['data']['trainStatus'] == 0) { Get.snackbar( 'error'.tr, // 错误 _decodeCorruptedText(body['data']['failedDesc']), snackPosition: SnackPosition.TOP, ); } } } } // 取消 void cancel() { text.value = ''; _record.stop(); Get.back(); } // 选择音色 void selectTone(Map data) { if (!data['isUsable'] || type.value == data['type']) return; type.value = data['type']; settingsController.timbreName.value = data['name']; _storage.write('timbre', data['type']); } }