uniapp视频压缩踩坑记录

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

视频格式背景

目前市面上主流的Android及iOS机,其所拍摄的视频大体分为两种格式,分别为H.264H.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 接口调用结束的回调函数(调用成功、失败都会执行)

实现细节

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…

猜你喜欢

转载自blog.csdn.net/weixin_43905402/article/details/126783174
今日推荐