Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
ethan
/
appframe
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Commit 59970d1c
authored
2025-10-13 22:15:51 +0800
by
tanghuan
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
dev
1 parent
7611a6bc
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
106 additions
and
38 deletions
lib/bloc/web_cubit.dart
lib/data/repositories/message/upload_file.dart
lib/ui/pages/web_page.dart
lib/utils/audio_util.dart
pubspec.lock
pubspec.yaml
lib/bloc/web_cubit.dart
View file @
59970d1
...
...
@@ -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
};
}
/// 清空录音
...
...
lib/data/repositories/message/upload_file.dart
View file @
59970d1
...
...
@@ -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
(
'上传的文件过大'
);
}
//分段大小
1
M
final
chunkSize
=
1024
*
1024
*
1
;
//分段大小
5
M
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
]});
...
...
lib/ui/pages/web_page.dart
View file @
59970d1
...
...
@@ -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
();
},
...
...
lib/utils/audio_util.dart
0 → 100644
View file @
59970d1
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
;
}
}
}
pubspec.lock
View file @
59970d1
...
...
@@ -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:
...
...
pubspec.yaml
View file @
59970d1
...
...
@@ -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
...
...
Write
Preview
Styling with
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment