From dd269fec96e9e368b9006e472ad9d89f4c0d41b6 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Wed, 3 Dec 2025 20:01:27 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=A3=B0=E9=9F=B3=E5=A4=8D?= =?UTF-8?q?=E5=88=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/recorder_bottom_sheet.dart | 128 +++++++++++++++++- 1 file changed, 125 insertions(+), 3 deletions(-) diff --git a/lib/modules/voice_replication/views/recorder_bottom_sheet.dart b/lib/modules/voice_replication/views/recorder_bottom_sheet.dart index 018bd8842..82eb7f25a 100644 --- a/lib/modules/voice_replication/views/recorder_bottom_sheet.dart +++ b/lib/modules/voice_replication/views/recorder_bottom_sheet.dart @@ -1,8 +1,19 @@ +import 'dart:io'; +import 'dart:convert'; + import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:http/http.dart' as http; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:record/record.dart'; +import 'package:uuid/uuid.dart'; +import '../../../core/utils/permission_util.dart'; +import '../../../data/models/appconfig.dart'; import '../controllers/voice_replication_controller.dart'; class RecorderBottomSheet extends StatefulWidget { @@ -15,13 +26,24 @@ class RecorderBottomSheet extends StatefulWidget { class _RecorderBottomSheetState extends State { final _controller = Get.find(); + final GetStorage _storage = GetStorage(); final _record = AudioRecorder(); bool _isRecording = false; + String _resourceKey = ''; + String _resourceRegion = ''; + String _jessicaConsentId = ''; + + String _voiceTalentName = ''; + String _jessicaPersonalVoiceId = ''; + + String _path = ''; + @override void initState() { super.initState(); + _getDefaultData(); } @override @@ -30,6 +52,103 @@ class _RecorderBottomSheetState extends State { super.dispose(); } + void _getDefaultData() async { + _resourceKey = AppConfig.env('AZURE_SPEECH_KEY') ?? ''; + _resourceRegion = AppConfig.env('AZURE_SPEECH_REGION') ?? 'eastasia'; + _jessicaConsentId = const Uuid().v4(); + Map? userInfo = _storage.read('user_info'); + if (userInfo != null) { + _voiceTalentName = userInfo['user']['name'] ?? ''; + _jessicaPersonalVoiceId = userInfo['user']['uid'] ?? ''; + } + } + + void _startRecording() async { + setState(() { + _isRecording = true; + }); + await PermissionUtil.instance.requestPermission( + permissionType: Permission.microphone, + permissionName: 'microphonePermission'.tr, + explanationText: 'microphonePermissionExplanation'.tr, + permanentDenialText: 'microphonePermissionPermanentDenial'.tr, + ); + final directory = await getApplicationDocumentsDirectory(); + final recordDir = Directory('${directory.path}/VoiceReplication'); + if (!await recordDir.exists()) { + await recordDir.create(recursive: true); + } + _path = + '${directory.path}/VoiceReplication/recording_${DateTime.now().millisecondsSinceEpoch}.wav'; + await _record.start( + const RecordConfig( + encoder: AudioEncoder.wav, + sampleRate: 24000, + numChannels: 1, + ), + path: _path, + ); + } + + void _completeRecorder() async { + EasyLoading.show(status: '正在创建个人语音...'); + try { + await _record.stop(); + await _consents(); + await _personalvoices(); + } catch (e) { + Get.snackbar('error'.tr, e.toString(), snackPosition: SnackPosition.TOP); + } + EasyLoading.dismiss(); + } + + Future _consents() async { + final url = Uri.parse( + 'https://$_resourceRegion.api.cognitive.microsoft.com/customvoice/consents/$_jessicaConsentId?api-version=2024-02-01-preview'); + final request = http.MultipartRequest('POST', url); + request.headers['Ocp-Apim-Subscription-Key'] = _resourceKey; + + request.fields['description'] = 'Consent for $_voiceTalentName voice'; + request.fields['projectId'] = 'Voitrans'; + request.fields['voiceTalentName'] = _voiceTalentName; + request.fields['companyName'] = '深圳市云衍智能科技有限公司'; + request.fields['locale'] = 'zh-CN'; + + request.files.add(await http.MultipartFile.fromPath('audiodata', _path)); + final streamed = await request.send(); + final response = await http.Response.fromStream(streamed); + if (streamed.statusCode < 200 || streamed.statusCode >= 300) { + throw Exception('Failed to create consent: ${response.statusCode}'); + } + } + + Future _personalvoices() async { + final url = Uri.parse( + 'https://$_resourceRegion.api.cognitive.microsoft.com/customvoice/personalvoices/$_jessicaPersonalVoiceId?api-version=2024-02-01-preview'); + final request = http.MultipartRequest('POST', url); + request.headers['Ocp-Apim-Subscription-Key'] = _resourceKey; + + request.fields['projectId'] = 'Voitrans'; + request.fields['consentId'] = _jessicaConsentId; + + request.files.add(await http.MultipartFile.fromPath('audiodata', _path)); + final streamed = await request.send(); + final response = await http.Response.fromStream(streamed); + if (streamed.statusCode >= 200 && streamed.statusCode < 300) { + final bodyText = utf8.decode(response.bodyBytes, allowMalformed: true); + if (bodyText.isNotEmpty) { + final decoded = jsonDecode(bodyText); + print('------2-------$decoded'); + _controller.speakerProfileId.value = decoded['speakerProfileId']; + _storage.write('open_voice_replication', true); + _storage.write('speakerProfileId', decoded['speakerProfileId']); + } + } else { + throw Exception( + 'Failed to create personal voice: ${response.statusCode}'); + } + } + @override Widget build(BuildContext context) { return Padding( @@ -145,9 +264,12 @@ class _RecorderBottomSheetState extends State { Expanded( child: GestureDetector( onTap: () { - setState(() { - _isRecording = !_isRecording; - }); + if (_isRecording) { + Get.back(); + _completeRecorder(); + } else { + _startRecording(); + } }, child: Container( height: 48.w,