Commit 59970d1c by tanghuan

dev

1 parent 7611a6bc
......@@ -7,11 +7,13 @@ import 'package:appframe/config/routes.dart';
import 'package:appframe/data/models/message/h5_message.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/services/local_server_service.dart';
import 'package:appframe/utils/audio_util.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:fluwx/fluwx.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
......@@ -31,10 +33,12 @@ class WebState extends Equatable {
final int? userType;
final String? stuId;
/// 录音
final bool recorderIsInit;
final int recordState;
final String recordPath;
/// 播放
final bool playerIsInit;
final int playState;
final String playId;
......@@ -185,7 +189,7 @@ class WebCubit extends Cubit<WebState> {
finishLoading();
// 页面加载完成时,清空录音和音频(如果有打开过)
await clearRecording() ;
await clearRecording();
await clearAudio();
print('onPageFinished--------------------------------->');
......@@ -479,7 +483,9 @@ class WebCubit extends Cubit<WebState> {
}
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 {
......@@ -555,14 +561,27 @@ class WebCubit extends Cubit<WebState> {
var url = await _recorder!.stopRecorder();
await _recorder!.closeRecorder();
emit(state.copyWith(recorderIsInit: false, recordState: 0, recordPath: ''));
if (url == null || url.isEmpty) {
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,8 +61,7 @@ class UploadFileHandler extends MessageHandler {
}
/// 并行上传
static Future<Map<String, dynamic>> _uploadInParallel(
ApiService bxeApiService,
static Future<Map<String, dynamic>> _uploadInParallel(ApiService bxeApiService,
ApiService obsApiService,
String filePath, {
int maxConcurrency = 5,
......@@ -78,8 +77,8 @@ class UploadFileHandler extends MessageHandler {
throw Exception('上传的文件过大');
}
//分段大小1M
final chunkSize = 1024 * 1024 * 1;
//分段大小5M
final chunkSize = 1024 * 1024 * 5;
//分段总数
final totalChunks = (fileSize / chunkSize).ceil();
......@@ -98,17 +97,18 @@ class UploadFileHandler extends MessageHandler {
//生成唯一文件名,
// String objectKey = 'd2/test/file.csv';
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 = '';
Map<int, String> tagsMap = {};
// final futures = <Future>[];
final futures = <Future>[];
for (int i = 0; i < totalChunks; i++) {
// 控制并发数量
// if (futures.length >= maxConcurrency) {
// await Future.wait(futures);
// futures.clear();
// }
if (futures.length >= maxConcurrency) {
await Future.wait(futures);
futures.clear();
}
final start = i * chunkSize;
final actualChunkSize = (i + 1) * chunkSize > fileSize ? fileSize - start : chunkSize;
......@@ -127,15 +127,15 @@ class UploadFileHandler extends MessageHandler {
chunkSignUrl = nextResult['signed_url'] as String;
}
await _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap);
// final future = _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap);
// futures.add(future);
// await _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap);
final future = _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap);
futures.add(future);
}
// 等待剩余的上传完成
// if (futures.isNotEmpty) {
// await Future.wait(futures);
// }
if (futures.isNotEmpty) {
await Future.wait(futures);
}
randomAccessFile.closeSync();
return {'objectKey': objectKey, 'bucket': bucket, 'uploadId': uploadId, 'tagsMap': tagsMap};
......@@ -149,21 +149,18 @@ class UploadFileHandler extends MessageHandler {
}
/// 每次上传前,请求后端获取签名信息
static Future<Map<String, dynamic>> _next(
ApiService bxeApiService,
static Future<Map<String, dynamic>> _next(ApiService bxeApiService,
String objectKey,
String bucket,
String uploadId,
int partNum,
) async {
int partNum,) async {
var endpoint = '$_signatureNextUrl?objectKey=$objectKey&bucket=$bucket&uploadId=$uploadId&partNum=$partNum';
final resp = await bxeApiService.get(endpoint);
return resp.data;
}
/// 上传段,按照最大重试次数进行上传重试
static Future<void> _uploadChunkWithRetry(
ApiService obsApiService,
static Future<void> _uploadChunkWithRetry(ApiService obsApiService,
String signUrl,
int chunkIndex,
Uint8List chunk,
......@@ -178,7 +175,6 @@ class UploadFileHandler extends MessageHandler {
tagsMap[chunkIndex + 1] = etags[0];
return; // 上传成功
} else {
print('Chunk $chunkIndex upload failed: ${resp.statusCode} ${resp.data}');
throw Exception('Chunk $chunkIndex upload failed: ${resp.statusCode}');
}
} catch (e) {
......@@ -205,13 +201,11 @@ class UploadFileHandler extends MessageHandler {
}
/// 请求合并文件
static Future<Response> _merge(
ApiService bxeApiService,
static Future<Response> _merge(ApiService bxeApiService,
String objectKey,
String bucket,
String uploadId,
Map<int, String> tagsMap,
) async {
Map<int, String> tagsMap,) async {
final parts = [];
for (int i = 1; i <= tagsMap.length; i++) {
parts.add({'partNumber': i, 'etag': tagsMap[i]});
......
......@@ -7,9 +7,6 @@ import 'package:webview_flutter/webview_flutter.dart';
class WebPage extends StatelessWidget {
const WebPage({super.key});
// @override
// State<WebPage> createState() => _WebPageState();
@override
Widget build(BuildContext buildContext) {
final Map<String, dynamic>? extraData = GoRouterState.of(buildContext).extra as Map<String, dynamic>?;
......@@ -46,7 +43,7 @@ class WebPage extends StatelessWidget {
automaticallyImplyLeading: false,
leading: state.beBack
? IconButton(
icon: const Icon(Icons.arrow_back_ios),
icon: const Icon(Icons.arrow_back),
onPressed: () {
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:
url: "https://pub.flutter-io.cn"
source: hosted
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:
dependency: transitive
description:
......@@ -705,10 +721,10 @@ packages:
dependency: transitive
description:
name: image_picker_android
sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e"
sha256: dd7a61daaa5896cc34b7bc95f66c60225ae6bee0d167dde0e21a9d9016cac0dc
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.8.13+1"
version: "0.8.13+4"
image_picker_for_web:
dependency: transitive
description:
......
......@@ -36,6 +36,7 @@ dependencies:
dio: ^5.9.0
equatable: ^2.0.7
exif: ^3.3.0
ffmpeg_kit_flutter_new_audio: ^1.1.0
file_picker: ^10.3.2
flutter_bloc: ^9.1.1
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!