uniapp视频压缩踩坑记录
Posted ㄏ、Forgetˊ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uniapp视频压缩踩坑记录相关的知识,希望对你有一定的参考价值。
最近在聊天APP中实现了一个视频上传发送的功能,在此记录一下所踩的坑。
视频格式背景
目前市面上主流的android及ios机,其所拍摄的视频大体分为两种格式,分别为H.264
和H.265
格式,这两种格式对于我们开发而言只需要理解:
H.264为兼容性高的视频格式,可以在绝大部分设备播放
H.265为高效存储的视频格式,可节约存储空间,缺点是兼容性差,其他设备可能无法播放
参考链接: H.264_百度百科,H.265_百度百科
相关实现API
(1)uni.chooseVideo选取视频
参数名 | 说明 |
---|---|
sourceType | album 从相册选视频,camera 使用相机拍摄,默认为:[‘album’, ‘camera’] |
extension | 根据文件拓展名过滤,每一项都不能是空字符串。默认不过滤。 |
compressed | 是否压缩所选的视频源文件,默认值为 true,需要压缩。 |
maxDuration | 拍摄视频最长拍摄时间,单位秒。最长支持 60 秒。 |
camera | ‘front’、‘back’,默认’back’ |
success | 接口调用成功,返回视频文件的临时文件路径,详见返回参数说明。 |
fail | 接口调用失败的回调函数 |
complete | 接口调用结束的回调函数(调用成功、失败都会执行) |
(2)uni.compressVideo压缩视频
参数名 | 说明 |
---|---|
src | 视频文件路径,可以是临时文件路径也可以是永久文件路径 |
quality | 压缩质量 |
bitrate | 码率,单位 kbps |
fps | 帧率 |
resolution | 相对于原视频的分辨率比例,取值范围(0, 1] |
success | 接口调用成功的回调函数 |
fail | 接口调用失败的回调函数 |
complete | 接口调用结束的回调函数(调用成功、失败都会执行) |
(3)uni.getVideoInfo获取视频详细信息
参数名 | 说明 |
---|---|
src | 视频文件路径,可以是临时文件路径也可以是永久文件路径(不支持网络地址) |
success | 接口调用成功的回调函数 |
fail | 接口调用失败的回调函数 |
complete | 接口调用结束的回调函数(调用成功、失败都会执行) |
实现细节
chooseVideo
API中集成了compressed
压缩功能,但在实际操作过程中,还存在有权限及压缩效果不可控的问题。以鸿蒙系统为例,不管你是否开启压缩,其前后选取回调中的视频大小均为原视频大小(即该参数无效)。但实际上,并非该参数没有效果,而是其准备压缩该视频时被拒绝了,原因是无权限,相关代码截图如下:
// 鸿蒙系统真机调试下
// demo1 使用uni.chooseVideo并开启compressed
uni.chooseVideo(
compressed: true,
success: (res) =>
// res.size获取的视频大小为原视频大小,即压缩无效
);
// demo2 chooseVideo不开启压缩,改用compressVideo走压缩
uni.chooseVideo(
compressed: false,
success: (res) =>
uni.compressVideo(
src: res.tempFilePath,
quality: 'low',
success: () => // 压缩成功
,
fail: (err) =>
// 压缩失败
console.log('err:', err);
);
);
运行结果:在鸿蒙系统中demo1不管是否开启了compressed
,其获取到的视频大小均为原视频大小;demo2中则在compressVideo
中走失败回调,报错如下图:
所以基本上可以判定,在鸿蒙系统中是无法直接选取后压缩视频的。
解决方案
一番查阅文档后,有了一个大致的方向,既然无法对回调所返回的临时文件进行压缩,那么我把该临时文件拷贝一份放到APP目录下的话,对拷贝文件做压缩就可以了吧?事不宜迟,博主直接上手实践
封装一个拷贝文件的方法,file-copy.js
该方法将本地文件拷贝指定的目标文件夹下,并可指定文件名,返回值为retry
则需要再调用一次
export default
/**
* 复制系统文件到APP存储中
* @param String localPath 文件本地路径
* @param String targetFolder 复制到的目标文件夹
* @param String fileName 文件名
* @returns 拷贝成功返回路径
*/
copySysFileToAPP(localPath, targetFolder, fileName)
return new Promise((resolve) =>
// #ifdef APP-PLUS
plus.io.resolveLocalFileSystemURL(
localPath,
(fileEntry) =>
console.log('获取文件成功', fileEntry);
plus.io.resolveLocalFileSystemURL(
`_doc/$targetFolder`,
(directoryEntry) =>
console.log('获取目录成功', directoryEntry);
fileEntry.copyTo(
directoryEntry,
fileName,
(res) =>
console.log('拷贝成功', res);
resolve(`_doc/$targetFolder/$fileName`); // 拷贝成功返回路径
,
(err) =>
console.log('拷贝失败', err);
resolve(false);
);
,
(directoryErr) =>
console.log('获取目录失败', directoryErr);
// 获取不到目标目录,需要手动创建该目录后,再重新获取目录拷贝
plus.io.resolveLocalFileSystemURL(
'_doc',
(docEntry) =>
console.log('获取_doc成功', docEntry);
docEntry.getDirectory(
targetFolder,
create: true,
exclusive: false
,
(createSuccessCB) =>
console.log('创建目录成功', createSuccessCB);
resolve('retry'); // 创建后返回重试
,
(createFailCB) =>
console.log('创建目录失败', createFailCB);
resolve(false);
);
,
(docErr) =>
console.log('获取_doc失败', docErr);
resolve(false);
);
);
,
(fileErr) =>
console.log('获取文件失败', fileErr);
resolve(false);
);
// #endif
);
;
在业务逻辑页面使用
<script>
import fileCopy from './file-copy.js';
export default
methods:
chooseVideo()
uni.chooseVideo(
compressed: false,
maxDuration: 60,
success: async(res) =>
let videoName = 'dafeizhu';
let videoPath = await fileCopy.copySysFileToAPP(res.tempFilePath, 'video', videoName);
if (videoPath === 'retry')
videoPath = await fileCopy.copySysFileToAPP(res.tempFilePath, 'video', videoName);
// 拷贝完之后,用新的文件路径走压缩
uni.compressVideo(
src: videoPath,
quality: 'low',
success: (compressRes) =>
console.log('压缩成功', compressRes);
,
fail: (err) =>
console.log('压缩失败', err);
)
);
</script>
真机测试,压缩成功!
结束了吗?还没!
前文提到的视频格式分为两种,这两种格式均可以通过compressVideo
压缩,压缩之后的视频格式为H.264
,故不需要再考虑视频可播放的问题。
这个作为一个拓展,讲一下如何在压缩视频的同时,又最大限度的保持视频的质量。
在compressVideo
接口中,除了quality
之外,还有bitrate
(码率),fps
(帧率),resolution
(相对于原视频的分辨率比例)这三个参数,官方文档上说明了当需要更精细的控制时,可指定 bitrate、fps、和 resolution,当 quality 传入时,这三个参数将被忽略。
博主在经过实际的上手操作后,发现直接指定quality
的话,压缩4K视频会导致失败,故只能使用bitrate
,fps
,resolution
来控制压缩效果,那么如何获取视频的这三个参数呢?getVideoInfo
接口派上用场。
在实际操作中,博主还发现了Android端获取本机拍摄的视频时,会存在视频的宽高颠倒的情况(图片也有类似的情况),故需要做特定的处理
let video = await new Promise((resolve) =>
uni.getVideoInfo(
src: res.tempFilePath,
success: (result) =>
console.log('视频信息', result, res);
if (this.$store.state.platform === 'android' && (result.orientation === 'right' || result.orientation === 'left'))
// Android端在HBuilderX版本为3.6.2以下,会出现获取纵向视频宽高值相反的BUG
// HBx3.6.2版本已修复,详见https://ask.dcloud.net.cn/question/151205
let width = res.height;
res.height = res.width;
res.width = width;
res.fps = Math.ceil(result.fps);
res.bitrate = result.bitrate;
resolve(res);
,
fail: () =>
resolve(false);
);
);
压缩规则制定如下:
限制视频分辨率上限为1080P(1920×1080),不足1080P则默认为原视频分辨率,超过则换算成比例;码率超过4096的,按4096算;保持原视频帧率
// 修改一下compressVideo参数
let ratio = Math.sqrt((1920 * 1080) / (video.width * video.height));
let resolution = ratio >= 1 ? 1 : ratio;
let bitrate = video.bitrate > 4 * 1024 ? 4 * 1024 : video.bitrate;
uni.compressVideo(
src: videoPath,
bitrate,
fps: video.fps,
resolution,
...
)
大功告成!
总结
关于本文中提及的直接用chooseVideo
压缩视频失败的问题,在前段时间已反馈给官方,而关于文末的压缩规则制定,是根据博主自身的需求出发考虑的,各位有需要可以参照着修修改改(不过这里面也有坑!如码率过高也会导致压缩失败)
每踩一个坑,都是一个成长的脚印,在官方对这些BUG问题无作为无反馈的情况下,只能硬着头皮找出路,明知山有虎,偏向虎山行!
Keep learning…
以上是关于uniapp视频压缩踩坑记录的主要内容,如果未能解决你的问题,请参考以下文章
uniapp 视频压缩插件 Ba-VideoCompressor
uniapp 视频压缩插件 Ba-VideoCompressor
Uniapp 图片选择插件(支持视频音频) Ba-MediaPicker
uniapp文件管理 文件列表 获取媒体文件 图片视频音频文档压缩包文件并实现可删除文件 图片文件列表 视频文件列表 音频文件列表 获取内存卡图片视频音频pdf xlxs docx txt