import 'dart:convert'; import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:just_audio/just_audio.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../../core/utils/logger.dart'; import '../../../core/utils/synchrodata.dart'; import '../../../core/utils/upload_oss.dart'; import '../../../data/services/ble_manager.dart'; import '../../../data/services/db/sqflite_api.dart'; import '../../../core/utils/permission_util.dart'; import 'package:share_plus/share_plus.dart'; import '../../../routes/app_routes.dart'; class MeetingController extends GetxController { final ScrollController listController = ScrollController(); final AudioPlayer _audioPlayer = AudioPlayer(); // 是否还有更多数据 bool isGetMore = true; bool isLoading = false; int page = 1; int addQuantity = 0; List selected = []; final appDocDir = getApplicationDocumentsDirectory(); List files = []; TextEditingController titleController = TextEditingController(); // 录音标题 RxList dataList = [].obs; // 蓝牙服务 final bleManager = Get.find(); List dataListTask = []; bool isDisposed = false; String filePath = ''; final String _appid = '2316081933'; final String _token = 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En'; final String _cluster = 'volc_auc_common_flash'; final String _serviceUrl = 'https://openspeech.bytedance.com/api/v1/auc'; Map _headers = {}; @override void onInit() { super.onInit(); listController.addListener(_onScroll); _requestStoragePermission(); _headers = { 'Authorization': 'Bearer; $_token', 'Content-Type': 'application/json', }; _getDataList(); addMeetingAudio(); final Map args = Get.arguments ?? {}; // 检查参数是否存在且包含有效路径 if (args.isNotEmpty && args.containsKey('filePath') && args['filePath'] is String && args['filePath'].isNotEmpty) { final String path = args['filePath']; addAudio(path); } } @override void onClose() { listController.dispose(); titleController.dispose(); _audioPlayer.dispose(); isDisposed = true; super.onClose(); } void onPopInvokedWithResult(didPop, result) { Synchrodata.startSynchro(); } void _onScroll() { if (isGetMore && !isLoading && listController.position.pixels >= listController.position.maxScrollExtent - 100) { isLoading = true; page++; _getDataList(); } } // 请求本地数据 void _getDataList() async { List list = await SqfliteApi.getMeetingList((page - 1) * 10 + addQuantity); dataList.addAll(list); queryDataListTask(list); addQuantity = 0; if (list.length < 10) { isGetMore = false; } else { isGetMore = true; } isLoading = false; } // 请求存储权限 void _requestStoragePermission() async { PermissionUtil.instance .requestPermission( permissionType: Permission.storage, permissionName: "存储", explanationText: "我们需要访问存储权限,以便您可以选择并上传音频文件。", permanentDenialText: "检测到您已永久拒绝存储权限。如需继续使用此功能,请前往应用设置中手动开启存储权限。", ) .then((granted) { if (granted) { Logger.d("Permission", "用户授予了存储权限"); } else { Logger.d("Permission", "用户未授予存储权限"); } }); } void loadTranslatopus() async { final dir = Directory("/data/user/0/com.yunqiinnovation.deepsound/files/OpusAudio"); files = dir .listSync() .where((f) => f is File && (f.path.endsWith('.opus'))) .toList(); } void loadTranslatAudio() async { try { final appDir = await appDocDir; // 先 await 获取目录 final dir = Directory("${appDir.path}/TranslatAudio"); if (!await dir.exists()) { await dir.create(recursive: true); files = []; return; } files = dir .listSync() .where((f) => f is File && (f.path.endsWith('.mp3') || f.path.endsWith('.wav') || f.path.endsWith('.aac') || f.path.endsWith('.m4a') || f.path.endsWith('.mp4') || f.path.endsWith('.mov'))) .toList(); } catch (e) { files = []; } } Future shareAllTranslatAudio() async { final filePath = selected[0]; try { await Share.shareXFiles( [XFile(filePath)], text: '分享会议音频', ); EasyLoading.showToast('分享完成!'); } catch (e) { EasyLoading.showToast('分享失败: ${e.toString()}'); } } void addMeetingAudio() async { EasyLoading.show(status: '正在导入本地录音...'); try { final appDir = await appDocDir; final sourceDir = Directory("${appDir.path}/MeetingAudio/"); // 检查源目录是否存在 if (!await sourceDir.exists()) { EasyLoading.dismiss(); return; } // 创建目标目录(如果不存在) final destDir = Directory("${appDir.path}/LocalAudio/"); if (!await destDir.exists()) { await destDir.create(recursive: true); } // 获取目录下所有文件 final files = await sourceDir.list().where((entity) => entity is File).toList(); int addUp = 0; for (final fileEntity in files) { try { final file = File(fileEntity.path); final filePath = file.path; // 设置音频源获取时长 await _audioPlayer.setAudioSource(AudioSource.file(filePath)); int seconds = _audioPlayer.duration?.inSeconds ?? 0; // 获取文件修改时间 final fileStat = await file.stat(); final creationTime = fileStat.changed.millisecondsSinceEpoch; // 上传OSS final audiourl = await UploadOss.upload(filepath: filePath, rootDir: 'LocalAudio'); // 准备新路径 final fileName = filePath.split('/').last; final newPath = "${destDir.path}/$fileName"; // 构建数据 Map data = { 'title': fileName, 'type': 'LOCAL', 'seconds': seconds, 'filepath': newPath, // 存储新路径 'audiourl': audiourl, 'tasktype': 0, 'creationtime': creationTime, }; // 插入数据库 final meetingId = await SqfliteApi.insertMeeting(data); if (meetingId != null) { // 更新UI数据 dataList.insert(0, { 'id': meetingId, 'taskid': '', ...data, }); // 移动文件 await file.copy(newPath); await file.delete(); addQuantity++; addUp++; } } catch (e) { print("文件处理失败: ${fileEntity.path}, 错误: $e"); } } if (addUp > 0) EasyLoading.showSuccess('成功导入本地 $addUp 条录音'); } catch (e) { EasyLoading.showError('导入失败: $e'); } finally { EasyLoading.dismiss(); } } void addTranslatAudio() async { EasyLoading.show(status: '正在批量导入...'); for (final filePath in selected) { try { await _audioPlayer.setAudioSource(AudioSource.file(filePath)); final fileStat = await File(filePath).stat(); final creationTime = fileStat.changed.millisecondsSinceEpoch; // 时间戳 var audiourl = await UploadOss.upload( filepath: filePath, rootDir: 'TranslatAudio'); int seconds = _audioPlayer.duration?.inSeconds ?? 0; final fileName = filePath.split('/').last; Map data = { 'title': fileName, 'type': 'TRANSLAT', 'seconds': seconds, 'filepath': filePath, 'audiourl': audiourl, 'tasktype': 0, 'creationtime': creationTime, }; final meetingId = await SqfliteApi.insertMeeting(data); if (meetingId != null) { dataList.insert(0, { 'id': meetingId, 'taskid': '', ...data, }); addQuantity++; } } catch (e) { Logger.error('导入文件失败: $filePath, error: $e'); } } EasyLoading.dismiss(); } void addAudio([String? filePath]) async { EasyLoading.show(status: '正在导入...'); String fileName = ""; bool isParamProvided = filePath != null; // 更清晰的变量名 if (filePath != null) { // 正确截取文件名(跨平台安全方式) fileName = path.basename(filePath); } else { FilePickerResult? result = await FilePicker.platform.pickFiles( withData: true, ); if (result != null) { PlatformFile file = result.files.first; filePath = await _saveFile(file); fileName = file.name; } else { EasyLoading.dismiss(); return; } } await _audioPlayer.setAudioSource(AudioSource.file(filePath!)); var audiourl = await UploadOss.upload(filepath: filePath, rootDir: 'ExternalAudio'); int seconds = _audioPlayer.duration?.inSeconds ?? 0; int creationTime = DateTime.now().millisecondsSinceEpoch; Map data = { 'title': fileName, 'type': 'IMPORT', 'seconds': seconds, 'filepath': filePath, 'audiourl': audiourl, 'tasktype': 0, 'creationtime': creationTime, }; final meetingId = await SqfliteApi.insertMeeting(data); if (meetingId != null) { dataList.insert(0, { 'id': meetingId, 'taskid': '', ...data, }); addQuantity++; } if (isParamProvided) { Get.toNamed(Routes.meetingDetails, arguments: { 'id': meetingId, }); } EasyLoading.dismiss(); } Future _saveFile(PlatformFile file) async { final directory = await getApplicationDocumentsDirectory(); final directoryDir = Directory('${directory.path}/ExternalAudio'); if (!await directoryDir.exists()) { await directoryDir.create(recursive: true); } final String filePath = '${directory.path}/ExternalAudio/${file.name}'; final File localFile = File(filePath); await localFile.writeAsBytes(file.bytes!); return filePath; } // 修改标题 void editMeetingTitle(int id, String title) async { if (title.isNotEmpty) { SqfliteApi.editMeetingTitle(id, {'title': title}); int index = dataList.indexWhere((item) => item['id'] == id); if (index != -1) { final dataMap = Map.from(dataList[index]); dataMap['title'] = title; dataList[index] = dataMap; } } Get.back(); } // 删除数据 void deleteMeeting(int id, String filepath) async { dataList.removeWhere((item) => item['id'] == id); // 删除数据库中的记录 SqfliteApi.deleteMeeting(id); _deleteFile(filepath); addQuantity--; } // 删除文件 Future _deleteFile(String filepath) async { File file = File(filepath); bool fileExists = await file.exists(); if (fileExists) { await file.delete(); } } // 查询转写状态 void queryDataListTask(List list, {bool isAdd = true}) { for (var element in List.from(list)) { if (element['tasktype'] == 1) { if (isAdd) { dataListTask.add(element); } final taskId = element['taskid']; _fileRecognize(element['id'], taskId); } } } Future _fileRecognize(int id, String taskId) async { try { final startTime = DateTime.now().millisecondsSinceEpoch; if (isDisposed) return; var respDic = await _queryTask(taskId); var code = respDic['resp']['code'] as int; if (code == 1000) { await SqfliteApi.editMeetingTitle(id, {'tasktype': 2}); int index = dataList.indexWhere((item) => item['id'] == id); if (index != -1) { final dataMap = Map.from(dataList[index]); dataMap['tasktype'] = 2; dataList[index] = dataMap; int taskIndex = dataListTask.indexWhere((item) => item['id'] == id); if (taskIndex != -1) { dataListTask.removeAt(taskIndex); } } return; } else if (code < 2000) { Logger.debug("FileRecognize failed"); return; } while (!isDisposed) { await Future.delayed(const Duration(seconds: 2)); if (isDisposed) return; respDic = await _queryTask(taskId); code = respDic['resp']['code'] as int; if (code == 1000) { await SqfliteApi.editMeetingTitle(id, {'tasktype': 2}); int index = dataList.indexWhere((item) => item['id'] == id); if (index != -1) { final dataMap = Map.from(dataList[index]); dataMap['tasktype'] = 2; dataList[index] = dataMap; int taskIndex = dataListTask.indexWhere((item) => item['id'] == id); if (taskIndex != -1) { dataListTask.removeAt(taskIndex); } } return; } else if (code < 2000) { Logger.debug("FileRecognize failed"); return; } final currentTime = DateTime.now().millisecondsSinceEpoch; if (currentTime - startTime > 300 * 1000) { Logger.debug('wait time exceeds 300s'); return; } } } catch (e) { Logger.error('Error occurred: ${e.toString()}'); } } Future> _queryTask(String taskId) async { final queryBody = jsonEncode({ "appid": _appid, "token": _token, "id": taskId, "cluster": _cluster, }); try { final response = await http.post( Uri.parse('$_serviceUrl/query'), headers: _headers, body: queryBody, ); if (response.statusCode != 200) { throw Exception('Query failed with status: ${response.statusCode}'); } final respDic = jsonDecode(response.body); return respDic; } catch (e) { Logger.error('Query error: ${e.toString()}'); rethrow; } } }