更新记录
1.2.3(2026-07-13)
失败原因更容易定位
- 失败结果新增
errorCode、stage、command和detail字段。 stage可用于判断问题发生在输入检查、预览代理、媒体处理、输出校验或清理阶段。- 新增常见错误标识,例如输入文件不存在、任务冲突、预览代理失败和输出文件无效。
FLV 裁剪预览兼容
- 打开 FLV 裁剪页时,插件会自动生成临时 MP4 预览代理,避免 Android/iOS 原生播放器出现空白或黑屏。
- 临时代理只用于页面预览;最终导出仍使用原始视频文件,保证处理结果与原素材一致。
- 页面完成、取消、失败或清理缓存后,会自动删除临时预览文件。
输出文件更可靠
- 裁剪和压缩先写入临时文件,处理完成后再校验文件是否存在、非空且可读取。
- 只有校验通过才返回最终输出路径,并在结果中标记
outputValidated: true。 - 失败、取消或校验未通过时会清理半成品,避免缓存目录遗留不可播放文件。
1.2.1(2026-07-12)
更新文档
1.2.0(2026-07-12)
- 首次发布 Android/iOS 音视频裁剪、压缩与 FFmpeg/FFprobe 命令能力。
平台兼容性
uni-app(4.85)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | Android插件版本 | iOS | iOS插件版本 | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | 5.0 | 1.2.3 | 12 | 1.2.3 | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x(4.85)
| Chrome | Safari | Android | Android插件版本 | iOS | iOS插件版本 | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|---|---|
| - | - | 5.0 | 1.2.3 | 12 | 1.2.3 | - | - |
hl-ffmpeg-video
hl-ffmpeg-video 是一个运行在 Android 和 iOS 手机上的本地音视频处理插件。它可以裁剪、压缩、取帧、提取音频、拼接视频、生成 GIF,也可以直接执行 FFmpeg 和 FFprobe 命令。
所有处理都在设备本地完成。插件不会上传视频,也不会收集用户数据。
先了解这三件事
- 插件只处理本地视频文件,不能直接传网络 URL。
- 必须使用包含本插件的自定义基座或云端打包后的 App。标准基座不包含 FFmpeg 依赖。
- 所有处理结果都在回调里返回。成功时读取路径字段,例如
result.mp4、result.image或result.audio。
支持的平台
| 平台 | 最低版本 | 说明 |
|---|---|---|
| Android | Android 7.0(API 24) | 支持 armeabi-v7a、arm64-v8a 真机;不再打包 x86、x86_64 模拟器库 |
| iOS | iOS 12.0 | 支持 arm64 设备 |
| Web、小程序、HarmonyOS | 不支持 | 没有原生 FFmpeg 运行环境 |
第一次使用
1. 导入插件
插件放在项目的 uni_modules 目录时,不需要额外安装。
import { getVideoEditor } from '@/uni_modules/hl-ffmpeg-video'
const editor = getVideoEditor()
2. 选择视频并保存本地路径
uni.chooseVideo 返回的 tempFilePath 可以直接传给插件。
import { ref } from 'vue'
const videoPath = ref('')
function chooseVideo() {
uni.chooseVideo({
sourceType: ['album', 'camera'],
compressed: false,
success: (file) => {
videoPath.value = file.tempFilePath
}
})
}
如果你使用 _doc、_www 一类目录,请先转换为绝对路径:
const videoPath = plus.io.convertLocalFileSystemURL('_doc/source.mp4')
3. 用最小示例验证插件
先读取视频信息。看到 code: 0 即说明视频路径和原生依赖可用。
editor.getVideoInfo({ url: videoPath.value }, (result) => {
if (result.code === 0) {
console.log('视频尺寸:', result.width, result.height)
console.log('视频时长:', result.duration, '秒')
return
}
console.error('读取失败:', result.msg)
})
最常用:比例裁剪
下面的示例会打开原生裁剪页面。用户可以拖动裁剪框和时间滑块,点击“完成”后生成一条 9:16、720p 的视频。
editor.openCrop({
url: videoPath.value,
ratio: '9/16',
resolution: '720p',
mode: 'scale',
quality: 'hd',
mintime: 2000,
maxtime: 30,
codecs: 1,
fps: 30,
gop: 60
}, (result) => {
if (result.code === 1 && result.progress != null) {
console.log('处理进度:', result.progress)
return
}
if (result.code === 0) {
console.log('视频路径:', result.mp4)
console.log('封面路径:', result.image)
return
}
console.error(result.msg, result.detail || '')
})
mode 的含义:
| 值 | 适用场景 |
|---|---|
scale |
先按裁剪框裁剪,再输出指定比例。适合去掉画面边缘。 |
fill |
保留完整画面,等比缩放后用留边补满目标尺寸。适合不希望裁掉内容。 |
按平台导出,并控制文件大小
先调用 getExportPreview 查看最终尺寸、建议码率和预计大小。这个方法不会生成文件,适合在“导出”按钮前展示给用户。
const options = {
url: videoPath.value,
preset: 'wechat',
targetSizeMB: 10,
codecs: 1,
policy: 'queue'
}
editor.getExportPreview(options, (preview) => {
if (preview.code !== 0) return console.error(preview.msg)
console.log(`预计 ${preview.width}×${preview.height},${preview.estimatedSizeMB.toFixed(1)} MB`)
editor.videoToZip(options, (result) => {
if (result.code === 0) {
console.log('导出视频:', result.mp4)
console.log('实际大小:', result.actualSize)
console.log('是否达到目标:', result.targetSizeReached)
}
})
})
可用 preset:
| 值 | 适用场景 |
|---|---|
wechat |
微信发送;最长边默认限制为 720。 |
douyin |
竖屏短视频;默认 9:16,最长边 1080。 |
xiaohongshu |
小红书;默认 3:4,最长边 1080。 |
moments |
朋友圈;保持比例,最长边 1080。 |
fullHd |
保持清晰导出;保持比例,最长边 1080。 |
ratio、resolution、resolutionMax、fps、bitrate 和 codecs 等显式参数优先于预设。targetSizeMB 是目标而不是绝对保证;结果会通过 targetSizeReached 返回是否在允许误差内达到目标。
全部能力一览
| 方法 | 用途 | 成功结果的主要字段 |
|---|---|---|
openCrop |
打开固定比例裁剪页面 | mp4、image、duration、size |
openFreeCut |
打开自由裁剪页面 | mp4;或仅返回裁剪区域 |
videoToZip |
不打开页面,直接截取或压缩视频 | mp4、image、duration、size |
getExportPreview |
预估预设导出的尺寸、码率与大小,不写入文件 | width、height、bitrate、estimatedSize |
getVideoInfo |
读取时长、尺寸、帧率、码率等 | duration、width、height 等 |
getVideoFirstFrame |
提取第一帧图片 | image |
getVideoFrame |
提取指定时间点图片 | image |
generateThumbnails |
批量生成缩略图 | images、count |
extractAudio |
从视频提取 M4A 或 MP3 | audio、format |
removeAudio |
生成无声视频 | mp4 |
mergeAudioVideo |
用音频替换视频音轨 | mp4 |
concatVideos |
按顺序拼接多个视频 | mp4 |
videoToGif |
将视频片段转为 GIF | gif |
cancel |
取消当前媒体处理任务 | 无 |
deleteCache |
清理插件默认输出目录 | 无 |
FFmpeg |
执行 FFmpeg 命令并接收日志 | 回调 code、msg |
FFprobe |
执行 FFprobe 命令并接收日志 | 通过日志回调返回 |
ffmpegCancel |
取消 FFmpeg 启动的命令 |
无 |
常用调用示例
直接压缩到 720p
editor.videoToZip({
url: videoPath.value,
resolutionMax: '720p',
quality: 'hd',
codecs: 1,
startTime: 0,
endTime: 10_000
}, (result) => {
if (result.code === 0) console.log(result.mp4)
})
获取首帧和第 5 秒画面
editor.getVideoFirstFrame({ url: videoPath.value, width: 720 }, (result) => {
if (result.code === 0) console.log('首帧:', result.image)
})
editor.getVideoFrame({ url: videoPath.value, timeMs: 5000, width: 720 }, (result) => {
if (result.code === 0) console.log('第 5 秒画面:', result.image)
})
生成 6 张缩略图
editor.generateThumbnails({
url: videoPath.value,
startTime: 0,
intervalMs: 2000,
count: 6,
width: 320
}, (result) => {
if (result.code === 0) console.log(result.images)
})
提取音频、生成无声视频、替换音轨
editor.extractAudio({ url: videoPath.value, format: 'm4a', bitrate: '192k' }, (audioResult) => {
if (audioResult.code !== 0) return
editor.mergeAudioVideo({
url: videoPath.value,
audioUrl: audioResult.audio
}, (mergeResult) => {
if (mergeResult.code === 0) console.log('新视频:', mergeResult.mp4)
})
})
editor.removeAudio({ url: videoPath.value }, (result) => {
if (result.code === 0) console.log('无声视频:', result.mp4)
})
拼接视频和生成 GIF
editor.concatVideos({
urls: [firstVideoPath, secondVideoPath]
}, (result) => {
if (result.code === 0) console.log('拼接结果:', result.mp4)
})
editor.videoToGif({
url: videoPath.value,
startTime: 1000,
durationMs: 3000,
width: 480,
fps: 12
}, (result) => {
if (result.code === 0) console.log('GIF:', result.gif)
})
执行 FFmpeg 和 FFprobe
命令可以带 ffmpeg / ffprobe 前缀,也可以只传参数。路径中有空格时请用单引号包住。
const input = videoPath.value
const cover = plus.io.convertLocalFileSystemURL('_doc/cover.jpg')
editor.FFmpeg(
{ command: `ffmpeg -y -ss 1 -i '${input}' -frames:v 1 '${cover}'` },
(log) => console.log(log),
(result) => console.log(result)
)
editor.FFprobe(
{ command: `ffprobe -i '${input}' -show_streams -show_format` },
(log) => console.log(log)
)
参数说明
所有生成文件的方法
| 参数 | 类型 | 说明 |
|---|---|---|
url |
string |
本地视频绝对路径,必填。可以带 file://。 |
saveDirectory |
string |
输出目录绝对路径。省略时使用插件缓存目录。 |
裁剪、压缩和导出参数
| 参数 | 推荐值或范围 | 说明 |
|---|---|---|
resolution |
360p、480p、720p、1080p |
固定输出宽度。 |
resolutionMax |
同上 | 只在原视频超过该宽度时缩小。 |
ratio |
1/1、9/16、16/9 |
输出比例;不传则保持原比例。 |
mode |
scale、fill |
裁剪或留边策略。 |
quality |
ld、sd、hd、ssd |
预设码率等级。一般选择 hd。 |
bitrate |
例如 2500k |
明确指定视频码率,优先级高于 quality。 |
codecs |
0、1、2 |
0 为平台硬编码,1 为 H.264,2 为 H.265。iOS 会使用系统 VideoToolbox 编码器。 |
fps |
例如 30 |
输出帧率;0 保持原帧率。 |
gop |
例如 60 |
关键帧间隔;0 使用编码器默认值。 |
startTime、endTime |
毫秒 | 无界面截取的开始和结束时间;endTime: 0 表示到结尾。 |
saveToAlbum |
true、false |
成功后保存到系统相册。首次使用会请求系统权限。 |
crf 是软件编码质量参数。移动端首次接入建议先使用 quality,不必设置 crf。如同时设置 bitrate 和 crf,bitrate 优先。
裁剪界面额外参数
| 参数 | 说明 |
|---|---|
mintime |
最短可选时长,单位毫秒,默认 2000。 |
maxtime |
最长可选时长,单位秒,0 表示不限。 |
showModeButton |
是否显示“裁剪/填充”切换按钮。 |
backgroundColor |
裁剪页面背景色,例如 #141414。 |
setCropFrameColor |
裁剪框颜色。 |
isCrop |
仅 openFreeCut 使用。设为 false 时只返回选中的裁剪区域,不生成视频。 |
isHideCropFrame |
仅 openFreeCut 使用。是否隐藏裁剪框。 |
cropFrameParam |
网格配置,例如 { isShowGrid: true, gridNumber: 3, dragBlock4or8: true }。 |
任务策略与可靠性参数
同一进程里的 FFmpeg 取消操作会影响当前会话。连续发起处理任务时,请使用 policy 控制行为:
| 参数 | 可选值 | 默认值 | 说明 |
|---|---|---|---|
policy |
reject、replace、queue |
reject |
reject 立即返回忙碌错误;replace 取消正在处理的任务后执行新任务;queue 按提交顺序串行处理。 |
该参数适用于无界面处理和原始 FFmpeg 命令。裁剪页面始终只允许同时打开一个,避免多个原生页面竞争预览资源。
FLV 输入会生成临时 MP4 代理供原生裁剪页面预览;最终导出仍处理原始 FLV。代理在完成、取消、失败或调用 deleteCache() 后清理。
回调结果和进度
每个回调至少包含 code 和 msg。
code |
含义 | 处理方式 |
|---|---|---|
0 |
成功 | 读取当前方法的结果字段。 |
1 且有 progress |
正在处理 | progress 范围为 0-99。成功时即为 100%。 |
1 且没有 progress |
已取消 | 可以恢复页面按钮状态。 |
| 负数 | 失败 | 查看 msg,高级方法还可查看 detail。 |
媒体任务还会返回:
| 字段 | 含义 |
|---|---|
taskId |
当前处理任务的唯一标识;进度和最终结果保持一致。 |
stage |
input、preview、processing、output 或 scheduling。 |
errorCode |
机器可读错误码,例如 TASK_BUSY、INPUT_NOT_FOUND、PREVIEW_PROXY_FAILED、OUTPUT_INVALID。 |
command |
实际执行的 FFmpeg 命令;失败时配合 detail 定位问题。 |
outputValidated |
仅裁剪/压缩成功时为 true,表示输出文件已校验。 |
失败时建议同时输出 result.msg 和 result.detail:
if (result.code < 0) {
console.error(result.msg)
console.error(result.errorCode, result.stage, result.taskId)
console.error(result.detail || '')
}
常见问题
提示找不到 FFmpeg 或原生类
当前运行的不是新制作的自定义基座。重新制作并安装基座后再运行项目;普通标准基座无法加载本插件的原生依赖。
Android 16 KB 页面大小与 Google Play 上架
插件 Android 端已改为随插件提供的本地 ffmpeg-kit-full-gpl-16kb.aar,不再下载旧的 ffmpeg-kit-full-gpl:6.0-2.LTS。其中 arm64-v8a 的 FFmpeg 原生库使用 16 KB ELF 对齐,并保留 H.264、H.265、x264/x265 和硬编码能力。该发行包的最低系统版本为 Android 7.0(API 24),因此插件及项目的最低 Android 版本也同步设为 API 24。
本地 AAR 来源为 ffmpeg-kit-full-gpl-16kb 的 arm64_armv7a 发布包,文件校验值为:
SHA-256: b7168c385faf27526015452de3e4b40fac38bbbb6c0d7a0bdff9c67225316e23
修改插件后必须重新制作 Android 自定义基座或重新打发行包;旧基座中的 4 KB 原生库不会自动被替换。生成最终 APK 后,可执行下面命令检查 APK 的 16 KB ZIP 对齐:
zipalign -c -P 16 -v 4 app-release.apk
Android 官方也建议在最终发行包上同时检查 arm64-v8a 与 x86_64 的 ELF 对齐;本插件仅打包真机所需的 armeabi-v7a 和 arm64-v8a。
提示文件不存在或无法读取视频
确认传入的是 App 可访问的本地绝对路径。不要直接传 _doc/a.mp4,应先通过 plus.io.convertLocalFileSystemURL() 转换。
处理完成后找不到输出文件
先使用回调中的 mp4、image、audio 或 gif 路径。未设置 saveDirectory 时,文件位于插件缓存目录;调用 deleteCache() 会清理这些默认输出。
处理失败如何定位
打印 result.detail。其中包含 FFmpeg 日志和失败时实际执行的命令。常见原因包括输入路径错误、输出目录不可写、视频编码不受支持或参数超出视频尺寸。
iOS 比例裁剪或压缩失败
iOS 使用 h264_videotoolbox 或 hevc_videotoolbox 进行编码。请使用本插件最新版本制作自定义基座,不要在 iOS 上手动指定 libx264 或 libx265。

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 336
赞赏 0
下载 12541443
赞赏 1947
赞赏
京公网安备:11010802035340号