更新记录

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 命令。

所有处理都在设备本地完成。插件不会上传视频,也不会收集用户数据。

先了解这三件事

  1. 插件只处理本地视频文件,不能直接传网络 URL。
  2. 必须使用包含本插件的自定义基座或云端打包后的 App。标准基座不包含 FFmpeg 依赖。
  3. 所有处理结果都在回调里返回。成功时读取路径字段,例如 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。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

仅在 saveToAlbum 为 true 时请求系统媒体库写入权限

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集或上传用户数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

暂无用户评论。