|
|
|
@ -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<RecorderBottomSheet> { |
|
|
|
final _controller = Get.find<VoiceReplicationController>(); |
|
|
|
|
|
|
|
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<RecorderBottomSheet> { |
|
|
|
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<void> _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<void> _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<RecorderBottomSheet> { |
|
|
|
Expanded( |
|
|
|
child: GestureDetector( |
|
|
|
onTap: () { |
|
|
|
setState(() { |
|
|
|
_isRecording = !_isRecording; |
|
|
|
}); |
|
|
|
if (_isRecording) { |
|
|
|
Get.back(); |
|
|
|
_completeRecorder(); |
|
|
|
} else { |
|
|
|
_startRecording(); |
|
|
|
} |
|
|
|
}, |
|
|
|
child: Container( |
|
|
|
height: 48.w, |
|
|
|
|