|
|
|
@ -1,4 +1,5 @@ |
|
|
|
import 'dart:io'; |
|
|
|
import 'dart:async'; |
|
|
|
import 'package:get/get.dart'; |
|
|
|
import 'package:get_storage/get_storage.dart'; |
|
|
|
|
|
|
|
@ -21,6 +22,9 @@ class MeetingUploadService extends GetxService { |
|
|
|
|
|
|
|
bool _isUpload = false; |
|
|
|
int _handled = 0; //队列处理指针,表示当前循环语句处理的任务数量 |
|
|
|
static const Duration _fileStableTimeout = Duration(seconds: 20); |
|
|
|
static const Duration _fileStablePoll = Duration(milliseconds: 350); |
|
|
|
static const int _fileStableNeededCount = 3; |
|
|
|
|
|
|
|
@override |
|
|
|
void onInit() { |
|
|
|
@ -80,8 +84,26 @@ class MeetingUploadService extends GetxService { |
|
|
|
meetingId.value = element['id']; |
|
|
|
try { |
|
|
|
int totalProgress = 0; |
|
|
|
final filePath = (element['filepath'] ?? '').toString(); |
|
|
|
if (filePath.isEmpty) { |
|
|
|
throw Exception('Empty upload file path'); |
|
|
|
} |
|
|
|
final fileSizeBytes = await _waitForStableFileSize( |
|
|
|
filePath, |
|
|
|
timeout: _fileStableTimeout, |
|
|
|
pollInterval: _fileStablePoll, |
|
|
|
stableNeededCount: _fileStableNeededCount, |
|
|
|
); |
|
|
|
final isWav = filePath.toLowerCase().endsWith('.wav'); |
|
|
|
if (isWav) { |
|
|
|
final ok = await _isWavHeaderConsistent(filePath, fileSizeBytes); |
|
|
|
if (!ok) { |
|
|
|
throw Exception('WAV header is inconsistent with file size'); |
|
|
|
} |
|
|
|
} |
|
|
|
element['fileSizeBytes'] = fileSizeBytes; |
|
|
|
final audiourl = await UploadOss.upload( |
|
|
|
filepath: element['filepath'], |
|
|
|
filepath: filePath, |
|
|
|
rootDir: element['rootdir'], |
|
|
|
onSendProgress: (int count, int total) { |
|
|
|
// uploadProgress.value = count / total; |
|
|
|
@ -139,6 +161,87 @@ class MeetingUploadService extends GetxService { |
|
|
|
_isUpload = false; |
|
|
|
} |
|
|
|
|
|
|
|
Future<int> _waitForStableFileSize( |
|
|
|
String filePath, { |
|
|
|
Duration timeout = _fileStableTimeout, |
|
|
|
Duration pollInterval = _fileStablePoll, |
|
|
|
int stableNeededCount = _fileStableNeededCount, |
|
|
|
}) async { |
|
|
|
final file = File(filePath); |
|
|
|
final deadline = DateTime.now().add(timeout); |
|
|
|
int lastSize = -1; |
|
|
|
int stableCount = 0; |
|
|
|
while (DateTime.now().isBefore(deadline)) { |
|
|
|
final exists = await file.exists(); |
|
|
|
if (exists) { |
|
|
|
int size = 0; |
|
|
|
try { |
|
|
|
size = await file.length(); |
|
|
|
} catch (_) { |
|
|
|
size = 0; |
|
|
|
} |
|
|
|
if (size > 0) { |
|
|
|
if (size == lastSize) { |
|
|
|
stableCount += 1; |
|
|
|
} else { |
|
|
|
stableCount = 0; |
|
|
|
lastSize = size; |
|
|
|
} |
|
|
|
if (stableCount >= stableNeededCount) { |
|
|
|
return size; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
await Future.delayed(pollInterval); |
|
|
|
} |
|
|
|
throw TimeoutException('File not stable: $filePath', timeout); |
|
|
|
} |
|
|
|
|
|
|
|
Future<bool> _isWavHeaderConsistent( |
|
|
|
String filePath, int fileSizeBytes) async { |
|
|
|
RandomAccessFile? raf; |
|
|
|
try { |
|
|
|
raf = await File(filePath).open(mode: FileMode.read); |
|
|
|
final header = await raf.read(12); |
|
|
|
if (header.length < 12) return false; |
|
|
|
final riff = String.fromCharCodes(header.sublist(0, 4)); |
|
|
|
final wave = String.fromCharCodes(header.sublist(8, 12)); |
|
|
|
if (riff != 'RIFF' || wave != 'WAVE') return true; |
|
|
|
|
|
|
|
int offset = 12; |
|
|
|
const int maxScanBytes = 64 * 1024; |
|
|
|
while (offset + 8 <= fileSizeBytes && offset <= maxScanBytes) { |
|
|
|
await raf.setPosition(offset); |
|
|
|
final chunkHeader = await raf.read(8); |
|
|
|
if (chunkHeader.length < 8) return false; |
|
|
|
final chunkId = String.fromCharCodes(chunkHeader.sublist(0, 4)); |
|
|
|
final chunkSize = _readUint32Le(chunkHeader, 4); |
|
|
|
final chunkDataStart = offset + 8; |
|
|
|
final expectedEnd = chunkDataStart + chunkSize; |
|
|
|
if (chunkId == 'data') { |
|
|
|
return fileSizeBytes >= expectedEnd; |
|
|
|
} |
|
|
|
final paddedSize = chunkSize + (chunkSize % 2); |
|
|
|
offset = chunkDataStart + paddedSize; |
|
|
|
} |
|
|
|
return true; |
|
|
|
} catch (_) { |
|
|
|
return true; |
|
|
|
} finally { |
|
|
|
try { |
|
|
|
await raf?.close(); |
|
|
|
} catch (_) {} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
int _readUint32Le(List<int> bytes, int offset) { |
|
|
|
if (offset + 4 > bytes.length) return 0; |
|
|
|
return (bytes[offset]) | |
|
|
|
(bytes[offset + 1] << 8) | |
|
|
|
(bytes[offset + 2] << 16) | |
|
|
|
(bytes[offset + 3] << 24); |
|
|
|
} |
|
|
|
|
|
|
|
void removeUpload(int id, {bool isDeleteTask = false}) { |
|
|
|
Map uploadData = uploadList.firstWhere( |
|
|
|
(element) => element['id'] == id, |
|
|
|
|