Commit 59970d1c by tanghuan

dev

1 parent 7611a6bc
...@@ -7,11 +7,13 @@ import 'package:appframe/config/routes.dart'; ...@@ -7,11 +7,13 @@ import 'package:appframe/config/routes.dart';
import 'package:appframe/data/models/message/h5_message.dart'; import 'package:appframe/data/models/message/h5_message.dart';
import 'package:appframe/services/dispatcher.dart'; import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/services/local_server_service.dart'; import 'package:appframe/services/local_server_service.dart';
import 'package:appframe/utils/audio_util.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_sound/flutter_sound.dart'; import 'package:flutter_sound/flutter_sound.dart';
import 'package:fluwx/fluwx.dart'; import 'package:fluwx/fluwx.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
...@@ -31,10 +33,12 @@ class WebState extends Equatable { ...@@ -31,10 +33,12 @@ class WebState extends Equatable {
final int? userType; final int? userType;
final String? stuId; final String? stuId;
/// 录音
final bool recorderIsInit; final bool recorderIsInit;
final int recordState; final int recordState;
final String recordPath; final String recordPath;
/// 播放
final bool playerIsInit; final bool playerIsInit;
final int playState; final int playState;
final String playId; final String playId;
...@@ -185,7 +189,7 @@ class WebCubit extends Cubit<WebState> { ...@@ -185,7 +189,7 @@ class WebCubit extends Cubit<WebState> {
finishLoading(); finishLoading();
// 页面加载完成时,清空录音和音频(如果有打开过) // 页面加载完成时,清空录音和音频(如果有打开过)
await clearRecording() ; await clearRecording();
await clearAudio(); await clearAudio();
print('onPageFinished--------------------------------->'); print('onPageFinished--------------------------------->');
...@@ -479,7 +483,9 @@ class WebCubit extends Cubit<WebState> { ...@@ -479,7 +483,9 @@ class WebCubit extends Cubit<WebState> {
} }
final directory = await getTemporaryDirectory(); final directory = await getTemporaryDirectory();
String recordPath = '${directory.path}/${Uuid().v5(Namespace.url.value, 'www.banxiaoer.com')}_record.aac'; // String recordPath = '${directory.path}/${Uuid().v5(Namespace.url.value, 'www.banxiaoer.com')}_record.aac';
// String recordPath = '${directory.path}/${Uuid().v4()}_record.aac';
String recordPath = '${directory.path}/${Uuid().v4()}_record.m4a';
// 打开录音器 // 打开录音器
try { try {
...@@ -555,14 +561,27 @@ class WebCubit extends Cubit<WebState> { ...@@ -555,14 +561,27 @@ class WebCubit extends Cubit<WebState> {
var url = await _recorder!.stopRecorder(); var url = await _recorder!.stopRecorder();
await _recorder!.closeRecorder(); await _recorder!.closeRecorder();
emit(state.copyWith(recorderIsInit: false, recordState: 0, recordPath: ''));
if (url == null || url.isEmpty) { if (url == null || url.isEmpty) {
throw Exception("录音失败"); throw Exception("录音失败");
} }
// 获取音频文件时长
emit(state.copyWith(recorderIsInit: false, recordState: 0, recordPath: '')); var tempDir = await getTemporaryDirectory();
String fileName = path.basenameWithoutExtension(url);
String mp3Path = '${tempDir.path}/$fileName.mp3';
// var convertResult = await compute(AudioUtil.convertAacToMp3, {'accPath': url, 'mp3Path': mp3Path});
var convertResult = await AudioUtil.convertAacToMp3({'accPath': url, 'mp3Path': mp3Path});
if (!convertResult) {
throw Exception("录音转码失败");
}
// 时长
// var duration = await AudioUtil.getAudioDuration(mp3Path);
var duration = await AudioUtil.getAudioDuration(url);
return {'tempFilePath': url}; return {'tempFilePath': mp3Path, 'duration': duration.inSeconds};
} }
/// 清空录音 /// 清空录音
......
...@@ -61,12 +61,11 @@ class UploadFileHandler extends MessageHandler { ...@@ -61,12 +61,11 @@ class UploadFileHandler extends MessageHandler {
} }
/// 并行上传 /// 并行上传
static Future<Map<String, dynamic>> _uploadInParallel( static Future<Map<String, dynamic>> _uploadInParallel(ApiService bxeApiService,
ApiService bxeApiService, ApiService obsApiService,
ApiService obsApiService, String filePath, {
String filePath, { int maxConcurrency = 5,
int maxConcurrency = 5, }) async {
}) async {
//判断文件 //判断文件
File file = File(filePath); File file = File(filePath);
if (!file.existsSync()) { if (!file.existsSync()) {
...@@ -78,8 +77,8 @@ class UploadFileHandler extends MessageHandler { ...@@ -78,8 +77,8 @@ class UploadFileHandler extends MessageHandler {
throw Exception('上传的文件过大'); throw Exception('上传的文件过大');
} }
//分段大小1M //分段大小5M
final chunkSize = 1024 * 1024 * 1; final chunkSize = 1024 * 1024 * 5;
//分段总数 //分段总数
final totalChunks = (fileSize / chunkSize).ceil(); final totalChunks = (fileSize / chunkSize).ceil();
...@@ -98,17 +97,18 @@ class UploadFileHandler extends MessageHandler { ...@@ -98,17 +97,18 @@ class UploadFileHandler extends MessageHandler {
//生成唯一文件名, //生成唯一文件名,
// String objectKey = 'd2/test/file.csv'; // String objectKey = 'd2/test/file.csv';
var uuid = Uuid(); var uuid = Uuid();
String objectKey = '${uuid.v5(Namespace.url.value, 'www.banxiaoer.com')}${path.extension(file.path)}'; // String objectKey = '${uuid.v5(Namespace.url.value, 'www.banxiaoer.com')}${path.extension(file.path)}';
String objectKey = '${uuid.v4()}${path.extension(file.path)}';
String uploadId = ''; String uploadId = '';
Map<int, String> tagsMap = {}; Map<int, String> tagsMap = {};
// final futures = <Future>[]; final futures = <Future>[];
for (int i = 0; i < totalChunks; i++) { for (int i = 0; i < totalChunks; i++) {
// 控制并发数量 // 控制并发数量
// if (futures.length >= maxConcurrency) { if (futures.length >= maxConcurrency) {
// await Future.wait(futures); await Future.wait(futures);
// futures.clear(); futures.clear();
// } }
final start = i * chunkSize; final start = i * chunkSize;
final actualChunkSize = (i + 1) * chunkSize > fileSize ? fileSize - start : chunkSize; final actualChunkSize = (i + 1) * chunkSize > fileSize ? fileSize - start : chunkSize;
...@@ -127,15 +127,15 @@ class UploadFileHandler extends MessageHandler { ...@@ -127,15 +127,15 @@ class UploadFileHandler extends MessageHandler {
chunkSignUrl = nextResult['signed_url'] as String; chunkSignUrl = nextResult['signed_url'] as String;
} }
await _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap); // await _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap);
// final future = _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap); final future = _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap);
// futures.add(future); futures.add(future);
} }
// 等待剩余的上传完成 // 等待剩余的上传完成
// if (futures.isNotEmpty) { if (futures.isNotEmpty) {
// await Future.wait(futures); await Future.wait(futures);
// } }
randomAccessFile.closeSync(); randomAccessFile.closeSync();
return {'objectKey': objectKey, 'bucket': bucket, 'uploadId': uploadId, 'tagsMap': tagsMap}; return {'objectKey': objectKey, 'bucket': bucket, 'uploadId': uploadId, 'tagsMap': tagsMap};
...@@ -149,27 +149,24 @@ class UploadFileHandler extends MessageHandler { ...@@ -149,27 +149,24 @@ class UploadFileHandler extends MessageHandler {
} }
/// 每次上传前,请求后端获取签名信息 /// 每次上传前,请求后端获取签名信息
static Future<Map<String, dynamic>> _next( static Future<Map<String, dynamic>> _next(ApiService bxeApiService,
ApiService bxeApiService, String objectKey,
String objectKey, String bucket,
String bucket, String uploadId,
String uploadId, int partNum,) async {
int partNum,
) async {
var endpoint = '$_signatureNextUrl?objectKey=$objectKey&bucket=$bucket&uploadId=$uploadId&partNum=$partNum'; var endpoint = '$_signatureNextUrl?objectKey=$objectKey&bucket=$bucket&uploadId=$uploadId&partNum=$partNum';
final resp = await bxeApiService.get(endpoint); final resp = await bxeApiService.get(endpoint);
return resp.data; return resp.data;
} }
/// 上传段,按照最大重试次数进行上传重试 /// 上传段,按照最大重试次数进行上传重试
static Future<void> _uploadChunkWithRetry( static Future<void> _uploadChunkWithRetry(ApiService obsApiService,
ApiService obsApiService, String signUrl,
String signUrl, int chunkIndex,
int chunkIndex, Uint8List chunk,
Uint8List chunk, Map<int, String> tagsMap, {
Map<int, String> tagsMap, { int maxRetries = 3,
int maxRetries = 3, }) async {
}) async {
for (int attempt = 0; attempt <= maxRetries; attempt++) { for (int attempt = 0; attempt <= maxRetries; attempt++) {
try { try {
final resp = await _uploadChunk(obsApiService, signUrl, chunk); final resp = await _uploadChunk(obsApiService, signUrl, chunk);
...@@ -178,7 +175,6 @@ class UploadFileHandler extends MessageHandler { ...@@ -178,7 +175,6 @@ class UploadFileHandler extends MessageHandler {
tagsMap[chunkIndex + 1] = etags[0]; tagsMap[chunkIndex + 1] = etags[0];
return; // 上传成功 return; // 上传成功
} else { } else {
print('Chunk $chunkIndex upload failed: ${resp.statusCode} ${resp.data}');
throw Exception('Chunk $chunkIndex upload failed: ${resp.statusCode}'); throw Exception('Chunk $chunkIndex upload failed: ${resp.statusCode}');
} }
} catch (e) { } catch (e) {
...@@ -205,13 +201,11 @@ class UploadFileHandler extends MessageHandler { ...@@ -205,13 +201,11 @@ class UploadFileHandler extends MessageHandler {
} }
/// 请求合并文件 /// 请求合并文件
static Future<Response> _merge( static Future<Response> _merge(ApiService bxeApiService,
ApiService bxeApiService, String objectKey,
String objectKey, String bucket,
String bucket, String uploadId,
String uploadId, Map<int, String> tagsMap,) async {
Map<int, String> tagsMap,
) async {
final parts = []; final parts = [];
for (int i = 1; i <= tagsMap.length; i++) { for (int i = 1; i <= tagsMap.length; i++) {
parts.add({'partNumber': i, 'etag': tagsMap[i]}); parts.add({'partNumber': i, 'etag': tagsMap[i]});
......
...@@ -7,9 +7,6 @@ import 'package:webview_flutter/webview_flutter.dart'; ...@@ -7,9 +7,6 @@ import 'package:webview_flutter/webview_flutter.dart';
class WebPage extends StatelessWidget { class WebPage extends StatelessWidget {
const WebPage({super.key}); const WebPage({super.key});
// @override
// State<WebPage> createState() => _WebPageState();
@override @override
Widget build(BuildContext buildContext) { Widget build(BuildContext buildContext) {
final Map<String, dynamic>? extraData = GoRouterState.of(buildContext).extra as Map<String, dynamic>?; final Map<String, dynamic>? extraData = GoRouterState.of(buildContext).extra as Map<String, dynamic>?;
...@@ -46,7 +43,7 @@ class WebPage extends StatelessWidget { ...@@ -46,7 +43,7 @@ class WebPage extends StatelessWidget {
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
leading: state.beBack leading: state.beBack
? IconButton( ? IconButton(
icon: const Icon(Icons.arrow_back_ios), icon: const Icon(Icons.arrow_back),
onPressed: () { onPressed: () {
ctx.read<WebCubit>().handleBack(); ctx.read<WebCubit>().handleBack();
}, },
......
import 'dart:convert';
import 'package:ffmpeg_kit_flutter_new_audio/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_new_audio/ffprobe_kit.dart';
import 'package:ffmpeg_kit_flutter_new_audio/return_code.dart';
class AudioUtil {
/// 转码
static Future<bool> convertAacToMp3(Map<String, dynamic> params) async {
final aacPath = params['accPath'] as String;
final mp3Path = params['mp3Path'] as String;
final session = await FFmpegKit.execute('-i "$aacPath" -vn -ar 44100 -ac 2 -ab 192k -f mp3 "$mp3Path"');
final returnCode = await session.getReturnCode();
return ReturnCode.isSuccess(returnCode);
}
/// 获取音频时长
static Future<Duration> getAudioDuration(String filePath) async {
try {
String command = '-v quiet -print_format json -show_format "$filePath"';
var session = await FFprobeKit.execute(command);
final returnCode = await session.getReturnCode();
if (ReturnCode.isSuccess(returnCode)) {
var output = await session.getOutput();
var json = jsonDecode(output!);
var format = json['format'];
var duration = format['duration'];
final durationInSeconds = double.tryParse(duration) ?? 0.0;
return Duration(seconds: durationInSeconds.toInt());
}
return Duration.zero;
} catch (e) {
return Duration.zero;
}
}
}
...@@ -337,6 +337,22 @@ packages: ...@@ -337,6 +337,22 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
ffmpeg_kit_flutter_new_audio:
dependency: "direct main"
description:
name: ffmpeg_kit_flutter_new_audio
sha256: "2a7756ee7882e8350624dacb012bd785ba2c1a4ad5f229f16c3f0a63f7bdd479"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
ffmpeg_kit_flutter_platform_interface:
dependency: transitive
description:
name: ffmpeg_kit_flutter_platform_interface
sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.1"
file: file:
dependency: transitive dependency: transitive
description: description:
...@@ -705,10 +721,10 @@ packages: ...@@ -705,10 +721,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image_picker_android name: image_picker_android
sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" sha256: dd7a61daaa5896cc34b7bc95f66c60225ae6bee0d167dde0e21a9d9016cac0dc
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.8.13+1" version: "0.8.13+4"
image_picker_for_web: image_picker_for_web:
dependency: transitive dependency: transitive
description: description:
......
...@@ -36,6 +36,7 @@ dependencies: ...@@ -36,6 +36,7 @@ dependencies:
dio: ^5.9.0 dio: ^5.9.0
equatable: ^2.0.7 equatable: ^2.0.7
exif: ^3.3.0 exif: ^3.3.0
ffmpeg_kit_flutter_new_audio: ^1.1.0
file_picker: ^10.3.2 file_picker: ^10.3.2
flutter_bloc: ^9.1.1 flutter_bloc: ^9.1.1
flutter_localization: ^0.3.3 flutter_localization: ^0.3.3
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!