uniapp视频压缩踩坑记录

10 篇文章 1 订阅

最近在聊天APP中实现了一个视频上传发送的功能,在此记录一下所踩的坑。

视频格式背景

目前市面上主流的Android及iOS机,其所拍摄的视频大体分为两种格式,分别为H.264H.265格式,这两种格式对于我们开发而言只需要理解:
H.264为兼容性高的视频格式,可以在绝大部分设备播放
H.265为高效存储的视频格式,可节约存储空间,缺点是兼容性差,其他设备可能无法播放
参考链接: H.264_百度百科H.265_百度百科

相关实现API

(1)uni.chooseVideo选取视频
参数名说明
sourceTypealbum 从相册选视频,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接口调用结束的回调函数(调用成功、失败都会执行)

实现细节

chooseVideoAPI中集成了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视频会导致失败,故只能使用bitratefpsresolution来控制压缩效果,那么如何获取视频的这三个参数呢?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…

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值