pub.dev 上有几个提供视频压缩的著名软件包。我已经尝试过它们以及其他粗略的软件包,但一旦视频达到 300MB 左右,没有一个可以很好地工作。它们在各种平台和硬件上崩溃或存在其他问题。即视频压缩器和光压缩器。GH 提交和支持也与我在 pub.dev 中看到的视频压缩包有关。PR 没有被拉进来,问题也没有及时解决,对于最近的 Android APK 更新来说,有些问题相当严重。所以这不是我想要的依赖堆栈中的东西。
\n我正在使用 FlutterFire 上传到 Google Cloud Storage。虽然我的代码确实使用FireBaseStorage 上传任务进行上传,但它无法在客户端压缩或在应用程序关闭时处理后台上传。
\n因此,目前在服务器端,我有一个在文件上传时触发的 GCF。然后我使用nodejs ffmpeg,它被烘焙到GCF中以压缩服务器端并转换为H264。最后删除原来上传的大视频并将压缩视频保存到存储中。
\n这个解决方案是有效的,但是根据用户的连接以及他们是否在wifi上,可能需要很长的时间,并且当它失败或用户关闭应用程序时,我当前的解决方案是无用的。
\n我希望 Android 和 iOS 上有一个可靠的本机库,我可以利用它,自信地执行从任何格式到 H264 的压缩和转换,并且无论我的应用程序是关闭的还是在后台,都允许上传到 GC 存储。有什么想法吗?我希望这是 FlutterFire 云存储处理的标准!
\n我还没有测试flutter_ffmpeg,但这只是因为有些人说它在客户端上运行得很慢。再说一次,Flutter/Dart 可以访问原生编写的代码,但我不知道在 Android/iOS 上从哪里开始才能以正确的方式做到这一点。我知道这就是某些软件包正在做的事情,但它们不适用于大型视频,所以我希望有人能在 Android 和 iOS 上为我指明正确的方向。
\n我的代码用于处理上传任务到 GC 存储。
\n \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xe2\x80\x8bFuture<\xe2\x80\x8bvoid\xe2\x80\x8b>\xe2\x80\x8b\xc2\xa0\xe2\x80\x8buploadTask\xe2\x80\x8b({ \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x80\x8brequired\xe2\x80\x8b\xc2\xa0\xe2\x80\x8bWidgetRef\xe2\x80\x8b\xc2\xa0ref, \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x80\x8brequired\xe2\x80\x8b\xc2\xa0\xe2\x80\x8bFile\xe2\x80\x8b\xc2\xa0file, \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x80\x8brequired\xe2\x80\x8b\xc2\xa0\xe2\x80\x8bString\xe2\x80\x8b\xc2\xa0objectPath, \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x80\x8bSettableMetadata\xe2\x80\x8b?\xe2\x80\x8b\xc2\xa0metaData, \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x80\x8bbool\xe2\x80\x8b\xc2\xa0deleteAfterUpload\xc2\xa0\xe2\x80\x8b=\xe2\x80\x8b\xc2\xa0\xe2\x80\x8bfalse\xe2\x80\x8b, \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x80\x8bbool\xe2\x80\x8b\xc2\xa0displayProgressNotification\xc2\xa0\xe2\x80\x8b=\xe2\x80\x8b\xc2\xa0\xe2\x80\x8bfalse\xe2\x80\x8b, \n \xe2\x80\x8b\xc2\xa0\xc2\xa0})\xc2\xa0\xe2\x80\x8basync\xe2\x80\x8b\xc2\xa0{ \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x80\x8bString\xe2\x80\x8b\xc2\xa0filePath\xc2\xa0\xe2\x80\x8b=\xe2\x80\x8b\xc2\xa0file.path; \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0filename\xc2\xa0\xe2\x80\x8b=\xe2\x80\x8b\xc2\xa0\xe2\x80\x8bbasename\xe2\x80\x8b(file.path); \n \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x80\x8b///\xc2\xa0Remove\xc2\xa0any\xc2\xa0instances\xc2\xa0of\xc2\xa0\'//\'\xc2\xa0from\xc2\xa0the\xc2\xa0path. \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x80\x8bfinal\xe2\x80\x8b\xc2\xa0\xe2\x80\x8bString\xe2\x80\x8b\xc2\xa0path\xc2\xa0\xe2\x80\x8b=\xe2\x80\x8b\xc2\xa0\xe2\x80\x8b\'$\xe2\x80\x8bstoragePath\xe2\x80\x8b/$\xe2\x80\x8bobjectPath\xe2\x80\x8b\'\xe2\x80\x8b.\xe2\x80\x8breplaceAll\xe2\x80\x8b(\xe2\x80\x8b\'//\'\xe2\x80\x8b,\xc2\xa0\xe2\x80\x8b\'/\'\xe2\x80\x8b); \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x80\x8bUploadTask\xe2\x80\x8b\xc2\xa0task\xc2\xa0\xe2\x80\x8b=\xe2\x80\x8b\xc2\xa0storage.\xe2\x80\x8bref\xe2\x80\x8b(path).\xe2\x80\x8bputFile\xe2\x80\x8b( \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x80\x8bFile\xe2\x80\x8b(filePath), \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0metaData, \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0); \n \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xe2\x80\x8b///\xc2\xa0Store\xc2\xa0UploadTask\xc2\xa0in\xc2\xa0StateNotifierProvider\xc2\xa0to\xc2\xa0monitor\xc2\xa0progress. \n \xe2\x80\x8b\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0ref.\xe2\x80\x8bread\xe2\x80\x8b(uploadingStateProvider.notifier).\xe2\x80\x8bmyUploadTask\xe2\x80\x8b(task); \n \xe2\x80\x8b\xc2\xa0\xc2\xa0}\nRun Code Online (Sandbox Code Playgroud)\n
我确实通过在客户端使用ffmpeg_kit_flutter_full_gpl包,然后在服务器端的 GCF 中再次使用 ffmpeg,在某种程度上解决了我原来帖子的问题和挫败感。总之:
onFinalizeGCF,我在上传的视频上再次运行 ffmpeg,服务器端的文件大小又减少了 77%,而视频质量没有任何损失。ResolutionPreset为high(720p),而不是max,最低可以为 1080p,并设置 ffmpeg-preset veryfast而不是medium默认值。相机和 ffmpeg 解决方案设置:
ResolutionPreset到high-preset veryfast2 分钟视频的转码结果统计:
onFinalizedGCF ffmpeg 转码:19MB(大小减少 77%)flutter_ffmpeg已存档,新的 ffmpeg flutter 包为ffmpeg_kit_flutter。
话虽这么说,我使用ffmpeg_kit_flutter在客户端而不是服务器端构建我的解决方案,并在上传之前对视频进行转码。
缺点:
lame,x264因此我必须安装完整的 gpl 包才能访问这些库。优点:
因此,您必须决定是否利大于弊,以及 720p 的质量是否足以播放。对我来说,720p 看起来非常适合在手机上播放视频,而 1080p 或更高版本则显得有些过分了。
我提供了示例代码(不是完整的类),供任何想要实施我的解决方案的人尝试。由于转码时间较长,显示进度表变得非常重要,这样用户就不会放弃该过程。您将看到我显示转码进度的简单解决方案。
pubspec.yaml
h264和libmp3lame编码器来生成最广泛播放的转码视频。dependencies:
camera: ^0.9.4+12
flutter_riverpod: ^1.0.3
ffmpeg_kit_flutter_full_gpl: ^4.5.1
wakelock: ^0.5.6
Run Code Online (Sandbox Code Playgroud)
RiverpodStateNotifier类将进度更新发送到 ui,以进行转码和上传到 firebase 存储。假设用户熟悉 Riverpod 和 StateNotifiers。
@immutable
class TranscodeUploadMessage {
const TranscodeUploadMessage({
required this.id,
required this.statusTitle,
required this.statusMessage,
required this.uploadPercentage,
required this.isRunning,
required this.completed,
required this.showSpinner,
required this.showPercentage,
required this.showError,
});
final int id;
final String statusTitle;
final String statusMessage;
final String uploadPercentage;
final bool isRunning;
final bool completed;
final bool showSpinner;
final bool showPercentage;
final bool showError;
TranscodeUploadMessage copyWith({
int? id,
String? statusTitle,
String? statusMessage,
String? uploadPercentage,
bool? isRunning,
bool? completed,
bool? showSpinner,
bool? showPercentage,
bool? showError,
}) {
return TranscodeUploadMessage(
id: id ?? this.id,
statusTitle: statusTitle ?? this.statusTitle,
statusMessage: statusMessage ?? this.statusMessage,
uploadPercentage: uploadPercentage ?? this.uploadPercentage,
isRunning: isRunning ?? this.isRunning,
completed: completed ?? this.completed,
showSpinner: showSpinner ?? this.showSpinner,
showPercentage: showSpinner ?? this.showPercentage,
showError: showError ?? this.showError,
);
}
}
class TranscodeUploadMessageNotifier
extends StateNotifier<List<TranscodeUploadMessage>> {
TranscodeUploadMessageNotifier() : super([]);
/// Since our state is immutable, we are not allowed to do
/// `state.add(message)`. Instead, we should create a new list of messages which
/// contains the previous items and the new one.
///
/// Using Dart's spread operator here is helpful!
void set(TranscodeUploadMessage message) {
state = [...state, message];
}
/// Our state is immutable. So we're making a new list instead of changing
/// the existing list.
void remove(int id) {
state = [
for (final message in state)
if (message.id != id) message,
];
}
/// Update message. Since our state is immutable, we need to make a copy of
/// the message. We're using our `copyWith` method implemented before to help
/// with that.
void update(TranscodeUploadMessage messageUpdated) {
state = [
for (final message in state)
if (message.id == messageUpdated.id)
/// Use copyWith to update a message
message.copyWith(
statusTitle: messageUpdated.statusTitle,
statusMessage: messageUpdated.statusMessage,
uploadPercentage: messageUpdated.uploadPercentage,
isRunning: messageUpdated.isRunning,
completed: messageUpdated.completed,
showSpinner: messageUpdated.showSpinner,
showPercentage: messageUpdated.showPercentage,
showError: messageUpdated.showError,
)
else
/// other messages, which there are not any at this time, are not
/// modified
message,
];
}
}
/// Using StateNotifierProvider to allow the UI to interact with our
/// TranscodeUploadMessageNotifier class.
final transcodeMessageProvider = StateNotifierProvider.autoDispose<
TranscodeUploadMessageNotifier, List<TranscodeUploadMessage>>((ref) {
return TranscodeUploadMessageNotifier();
});
Run Code Online (Sandbox Code Playgroud)
ffmpegkit 包运行 ffmpeg 命令并将转码统计信息发送到 StateNotifier。(缺少相当多的代码,但用伪代码来演示。)
/// By default, set to video ffmpeg command.
String ffmpegCommand = '-i $messageUri '
'-acodec aac '
'-vcodec libx264 '
'-f mp4 -preset veryfast '
'-movflags frag_keyframe+empty_moov '
'-crf 23 $newMessageUri';
if (_recordingType == RecordingType.audio) {
ffmpegCommand = '-vn '
'-i $messageUri '
'-y '
'-acodec libmp3lame '
'-f '
'mp3 '
'$newMessageUri';
}
/// Set the initial state notifier as we start transcoding.
ref
.read(transcodeMessageProvider.notifier)
.set(const TranscodeUploadMessage(
id: 1,
statusTitle: 'Transcoding Recording',
statusMessage: 'Your recording is being transcoded '
'before upload. Please do not navigate away from this screen.',
uploadPercentage: '0%',
isRunning: true,
completed: false,
showSpinner: false,
showPercentage: false,
showError: false,
));
await FFmpegKit.executeAsync(
ffmpegCommand,
(Session session) async {
final ReturnCode? returnCode = await session.getReturnCode();
if (ReturnCode.isSuccess(returnCode)) {
/// Transcoding is complete, now display uploading message
/// and spinner at 0%.
ref
.read(transcodeMessageProvider.notifier)
.update(const TranscodeUploadMessage(
id: 1,
statusTitle: 'Uploading Recording',
statusMessage:
'Your recording is now being '
'uploaded. Please do not navigate away from this screen.',
uploadPercentage: '0%',
isRunning: true,
completed: false,
showSpinner: true,
showPercentage: true,
showError: false,
));
/// Upload the now transcoded video/audio to cloud storage where
/// Use flutterfire firebase storage tasks to get upload
/// progress. Your firebase storage function can also
/// reuse the transcodeMessageProvider to send UI state
/// updates for the upload, which will happen very quickly
/// even on slow connections now that the recording size
/// is dramatically reduced.
await uploadRecordingToFirebaseCloudStorage(ref);
} else if (ReturnCode.isCancel(returnCode)) {
// Do something if canceled
} else {
// Do something with the error
}
},
(Log log) => debugPrint(log.getMessage()),
(Statistics statistic) {
/// Statistics provides a running transcoding progress meter.
int completePercentage = (statistic.getTime() * 100) ~/ _duration!;
ref
.read(transcodeMessageProvider.notifier)
.update(TranscodeUploadMessage(
id: 1,
statusTitle: 'Transcoding Recording',
statusMessage: 'Your recording is being '
'transcoded. Please do not navigate away from this screen.',
uploadPercentage: '$completePercentage%',
isRunning: true,
completed: false,
showSpinner: true,
showPercentage: true,
showError: false,
));
}).then((Session session) {
debugPrint(
'Async FFmpeg process started with sessionId ${session.getSessionId()}.');
}).catchError((error) async {
debugPrint('transcoding error: $error');
});
Run Code Online (Sandbox Code Playgroud)
使用 Riverpod Consumer通过观察 StateNotifier 并在 UI 中显示更新的状态来更新 UI。
Consumer(
builder: (context, watch, child) {
final List<TranscodeUploadMessage> messages =
ref.watch(transcodeMessageProvider);
if (messages.isEmpty) {
return const SizedBox.shrink();
}
final message = messages[0];
if (message.isRunning ||
message.completed ||
message.showError) {
// Display widgets with StateNotifier data
}
return const SizedBox.shrink();
},
)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1806 次 |
| 最近记录: |