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 552c2145
authored
2026-04-21 14:55:12 +0800
by
tanghuan
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
上传文件:1.针对视频文件增加转码和压缩的进度反馈;2.增加上传进度反馈。
1 parent
596a373b
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
154 additions
and
9 deletions
lib/bloc/web_cubit.dart
lib/config/constant.dart
lib/config/locator.dart
lib/data/repositories/message/upload_start_handler.dart
lib/services/dispatcher.dart
lib/utils/video_util.dart
lib/bloc/web_cubit.dart
View file @
552c214
...
...
@@ -408,6 +408,67 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
_controller
.
runJavaScript
(
script
);
}
/// 相应 uploadStart 指令
/// [unique] 消息唯一标识
/// [uploadId] 文件上传ID标识
///
void
sendUploadStartResponse
(
String
unique
,
String
uploadId
)
{
var
resp
=
{
'unique'
:
unique
,
'cmd'
:
'uploadStart'
,
'data'
:
{
'uploadId'
:
uploadId
,
'status'
:
1
,
'percent'
:
0
,
'totalPart'
:
0
,
'sendedPart'
:
0
,
'totalByte'
:
0
,
'sendedByte'
:
0
,
},
'errMsg'
:
''
,
};
_sendResponse
(
resp
);
}
/// 发送上传进度到 WebView
/// [unique] 消息唯一标识
/// [uploadId] 文件上传ID标识
/// [totalPart] 总分片数
/// [sendedPart] 已上传分片数
/// [totalByte] 总字节数
/// [sendedByte] 已上传字节数
void
sendUploadProgress
(
String
unique
,
String
uploadId
,
int
status
,
int
percent
,
int
totalPart
,
int
sendedPart
,
int
totalByte
,
int
sendedByte
)
{
var
resp
=
{
'unique'
:
unique
,
'cmd'
:
'uploadProgress'
,
'data'
:
{
'uploadId'
:
uploadId
,
'status'
:
status
,
'percent'
:
percent
,
'totalPart'
:
totalPart
,
'sendedPart'
:
sendedPart
,
'totalByte'
:
totalByte
,
'sendedByte'
:
sendedByte
,
},
'errMsg'
:
''
,
};
_sendResponse
(
resp
);
}
void
sendUploadEnd
(
String
unique
,
String
uploadId
,
String
url
,
{
String
errMsg
=
''
})
{
var
resp
=
{
'unique'
:
''
,
'cmd'
:
'uploadEnd'
,
'data'
:
{
'uploadId'
:
uploadId
,
'url'
:
url
,
},
'errMsg'
:
errMsg
};
_sendResponse
(
resp
);
}
void
finishLoading
()
{
emit
(
state
.
copyWith
(
loaded:
true
));
}
...
...
lib/config/constant.dart
View file @
552c214
...
...
@@ -24,7 +24,7 @@ class Constant {
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// obs文件分片上传的分片大小:5M
static
const
int
obsUploadChunkSize
=
1024
*
1024
*
5
;
static
const
int
obsUploadChunkSize
=
1024
*
1024
*
1
;
/// obs文件上传的逻辑前缀
static
const
String
obsLogicPrefix
=
EnvConfig
.
env
==
'dev'
?
'd2/pridel/user/'
:
'p2/unpridel/user/'
;
...
...
lib/config/locator.dart
View file @
552c214
...
...
@@ -30,6 +30,7 @@ import 'package:appframe/data/repositories/message/share_to_wx_handler.dart';
import
'package:appframe/data/repositories/message/storage_handler.dart'
;
import
'package:appframe/data/repositories/message/title_bar_handler.dart'
;
import
'package:appframe/data/repositories/message/upload_file.dart'
;
import
'package:appframe/data/repositories/message/upload_start_handler.dart'
;
import
'package:appframe/data/repositories/message/vibrate_short_handler.dart'
;
import
'package:appframe/data/repositories/message/video_info_handler.dart'
;
import
'package:appframe/data/repositories/message/wifi_info_handler.dart'
;
...
...
@@ -179,6 +180,7 @@ Future<void> setupLocator() async {
/// 上传文件
getIt
.
registerLazySingleton
<
MessageHandler
>(()
=>
UploadFileHandler
(),
instanceName:
'uploadFile'
);
getIt
.
registerLazySingleton
<
MessageHandler
>(()
=>
UploadStartHandler
(),
instanceName:
'uploadStart'
);
/// 下载文件
getIt
.
registerLazySingleton
<
MessageHandler
>(()
=>
DownloadFileHandler
(),
instanceName:
'downloadFile'
);
...
...
lib/data/repositories/message/upload_start_handler.dart
0 → 100644
View file @
552c214
This diff is collapsed.
Click to expand it.
lib/services/dispatcher.dart
View file @
552c214
...
...
@@ -4,6 +4,7 @@ import 'package:appframe/bloc/web_cubit.dart';
import
'package:appframe/config/locator.dart'
;
import
'package:appframe/data/models/message/h5_message.dart'
;
import
'package:appframe/data/models/message/h5_resp.dart'
;
import
'package:appframe/data/repositories/message/upload_start_handler.dart'
;
// 消息处理器抽象类
abstract
class
MessageHandler
{
...
...
@@ -56,11 +57,17 @@ class MessageDispatcher {
h5Message
.
cmd
==
"goLogin"
||
h5Message
.
cmd
.
startsWith
(
"setTitlebar"
)
||
h5Message
.
cmd
==
"audioPlay"
||
h5Message
.
cmd
==
"openLink"
)
{
h5Message
.
cmd
==
"openLink"
||
h5Message
.
cmd
==
"uploadStart"
)
{
handler
.
setCubit
(
webCubit
!);
handler
.
setMessage
(
message
);
}
// 针对 uploadStart 指令
if
(
h5Message
.
cmd
==
"uploadStart"
&&
handler
is
UploadStartHandler
)
{
handler
.
setCmdUnique
(
h5Message
.
unique
);
}
final
result
=
await
handler
.
handleMessage
(
h5Message
.
params
);
// 有些命令需要通过监听器调用Cubit,触发调用时不需要返回结果,不处理回调
if
(
result
==
null
)
{
...
...
lib/utils/video_util.dart
View file @
552c214
import
'dart:io'
;
import
'dart:async'
;
import
'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart'
;
import
'package:ffmpeg_kit_flutter_new/return_code.dart'
;
import
'package:ffmpeg_kit_flutter_new/statistics.dart'
;
class
VideoUtil
{
///
/// 将视频格式转换为mp4
/// 转码的同时,进行压缩
/// [onProgress] 进度回调,值范围 0.0 ~ 1.0
///
static
Future
<
bool
>
convertToMp4
(
String
inputPath
,
String
outputPath
)
async
{
static
Future
<
bool
>
convertToMp4
(
String
inputPath
,
String
outputPath
,
{
void
Function
(
int
progress
)?
onProgress
,
})
async
{
// 先获取视频总时长(微秒)
final
duration
=
await
_getVideoDuration
(
inputPath
);
String
cmd
;
if
(
Platform
.
isIOS
)
{
cmd
=
'-i "
$inputPath
" '
...
...
@@ -24,20 +34,47 @@ class VideoUtil {
'-crf 28 '
// 设置恒定速率因子CRF为28(中等压缩质量)
'-c:a aac '
// 设置音频编码器为AAC
'-b:a 128k '
// 设置音频比特率为128kbps
'-preset fast '
'-threads 0 '
'-strict experimental '
// 允许使用实验性编解码器功能
'-movflags faststart '
// 优化MP4文件结构,使视频可以快速启动播放
'-f mp4 '
// 指定输出格式为MP4
'"
$outputPath
"'
;
// 指定输出文件路径
}
final
session
=
await
FFmpegKit
.
execute
(
cmd
);
final
completer
=
Completer
<
bool
>();
FFmpegKit
.
executeAsync
(
cmd
,
(
session
)
async
{
final
returnCode
=
await
session
.
getReturnCode
();
return
ReturnCode
.
isSuccess
(
returnCode
);
completer
.
complete
(
ReturnCode
.
isSuccess
(
returnCode
));
},
null
,
(
Statistics
statistics
)
{
if
(
onProgress
!=
null
&&
duration
>
0
)
{
final
currentTime
=
statistics
.
getTime
();
final
progress
=
(
currentTime
/
duration
).
clamp
(
0.0
,
1.0
);
onProgress
((
progress
*
100
).
floor
());
}
},
);
return
completer
.
future
;
}
///
/// 通过 ffmpeg 压缩视频
/// [onProgress] 进度回调,值范围 0.0 ~ 1.0
///
static
Future
<
bool
>
compressVideo
(
String
inputPath
,
String
outputPath
,
String
quality
)
async
{
static
Future
<
bool
>
compressVideo
(
String
inputPath
,
String
outputPath
,
String
quality
,
{
void
Function
(
int
progress
)?
onProgress
,
})
async
{
final
duration
=
await
_getVideoDuration
(
inputPath
);
// 使用CRF模式进行压缩,值范围0-51,建议值18-28
// 高质量: CRF 18-20
// 中等质量: CRF 23-26
...
...
@@ -61,12 +98,50 @@ class VideoUtil {
'-crf
$crf
'
// 恒定速率因子(质量控制)
'-c:a aac '
// 音频编码器
'-b:a 128k '
// 音频比特率
'-preset medium '
// 编码预设
// '-preset medium ' // 编码预设
'-preset fast '
// 编码预设,编码速度显著提升,体积损失很小
'-threads 0 '
// 让 libx264 自动使用所有可用 CPU 核心,默认行为可能只用单核
'-movflags faststart '
// 优化MP4文件结构
'"
$outputPath
"'
;
// 输出文件
final
session
=
await
FFmpegKit
.
execute
(
cmd
);
final
completer
=
Completer
<
bool
>();
FFmpegKit
.
executeAsync
(
cmd
,
(
session
)
async
{
final
returnCode
=
await
session
.
getReturnCode
();
return
ReturnCode
.
isSuccess
(
returnCode
);
completer
.
complete
(
ReturnCode
.
isSuccess
(
returnCode
));
},
null
,
(
Statistics
statistics
)
{
if
(
onProgress
!=
null
&&
duration
>
0
)
{
final
currentTime
=
statistics
.
getTime
();
final
progress
=
(
currentTime
/
duration
).
clamp
(
0.0
,
1.0
);
onProgress
((
progress
*
100
).
floor
());
}
},
);
return
completer
.
future
;
}
/// 获取视频总时长,返回毫秒
static
Future
<
int
>
_getVideoDuration
(
String
videoPath
)
async
{
final
session
=
await
FFmpegKit
.
execute
(
'-i "
$videoPath
"'
,
);
final
output
=
await
session
.
getOutput
();
// 从 ffmpeg 输出中解析时长,格式如: Duration: 00:01:23.45
final
regex
=
RegExp
(
r'Duration:\s*(\d+):(\d+):(\d+)\.(\d+)'
);
final
match
=
regex
.
firstMatch
(
output
??
''
);
if
(
match
!=
null
)
{
final
hours
=
int
.
parse
(
match
.
group
(
1
)!);
final
minutes
=
int
.
parse
(
match
.
group
(
2
)!);
final
seconds
=
int
.
parse
(
match
.
group
(
3
)!);
final
ms
=
int
.
parse
(
match
.
group
(
4
)!);
return
(
hours
*
3600
+
minutes
*
60
+
seconds
)
*
1000
+
ms
*
10
;
}
return
0
;
}
/// 为视频文件生成缩略图
...
...
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