更新记录
1.0.0(2023-03-01)
- 首次发布
平台兼容性
Android |
Android CPU类型 |
iOS |
适用版本区间:5.0 - 12.0 |
armeabi-v7a:未测试,arm64-v8a:未测试,x86:未测试 |
适用版本区间:9 - 15 |
原生插件通用使用流程:
- 购买插件,选择该插件绑定的项目。
- 在HBuilderX里找到项目,在manifest的app原生插件配置中勾选模块,如需要填写参数则参考插件作者的文档添加。
- 根据插件作者的提供的文档开发代码,在代码中引用插件,调用插件功能。
- 打包自定义基座,选择插件,得到自定义基座,然后运行时选择自定义基座,进行log输出测试。
- 开发完毕后正式云打包
付费原生插件目前不支持离线打包。
Android 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/android
iOS 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/ios
注意事项:使用HBuilderX2.7.14以下版本,如果同一插件且同一appid下购买并绑定了多个包名,提交云打包界面提示包名绑定不一致时,需要在HBuilderX项目中manifest.json->“App原生插件配置”->”云端插件“列表中删除该插件重新选择
使用说明
创建插件对象
var videoCompressModule = uni.requireNativePlugin('VideoCompressUniPlugin-VideoCompressModule');
视频压缩
uni.showLoading({
title: '开始压缩'
});
let appendixPath = this.filePathList[index ? index : 0];
let params = {
//输入输出
inPath: plus.io.convertLocalFileSystemURL(appendixPath), //绝对路径
// outPath: '', //输出绝对路径,非必填,默认为输入路径文件夹下,文件名为时间戳.mp4
quality: 2,
}
videoCompressModule.compress(params, (res) => {
//done标识完成
if (res.done) {
uni.hideLoading();
}
}, (e) => {
uni.hideLoading();
uni.showToast({
title: e.msg ? e.msg : '压缩失败',
icon: "none"
})
});
传参
property |
description |
inPath |
必传,视频文件的完整路径,通过uni-file-picker等组件选择视频后会获取到文件路径,通过使用plus.io.convertLocalFileSystemURL(path)转换为完整的路径作为参数 |
outPath |
非必传,压缩完成的视频文件路径,默认为应用沙盒内 |
quality |
非必传,视频压缩质量:1-低质量,2-中等质量,3-高质量,4-最大长宽为640x480,5-最大长宽为960x540,6-最大长宽为1280x720,7-最大长宽为1920x1080;Android默认为中等质量,iOS默认最大长宽为960x540,如果对压缩后的视频质量有一定要求,但有不想文件太大,可以设置为5 |
startTime |
非必传,视频剪裁的开始时间,默认为0 |
duration |
非必传,视频剪裁的持续时间,开始时间+持续时间可以确定一个区间,默认设置为源视频的时长 |
deleteOrigin |
非必传,是否删除源视频,默认为false |
includeAudio |
非必传,是否包含音频信息 |
frameRate |
视频帧率,默认为30 |
回调参数
property |
description |
outPath |
压缩完成的视频文件的路径,默认为应用沙盒内 |
取消视频压缩
videoCompressModule.cancelCompression({}, (res) => {
uni.showToast({
title: res ? res : '取消成功',
})
}, (e) => {
uni.showToast({
title: e ? e.msg : '取消失败',
icon: "none"
})
});
传参
无
回调参数
无
获取视频信息
uni.showLoading({
title: '操作中..'
});
// path:视频压缩后的路径
videoCompressModule.getMediaInfo({
path: converted ? path : plus.io.convertLocalFileSystemURL(path) //本地视频绝对路径
}, (res) => {
uni.hideLoading();
console.log(res);
this.videoInfo = res;
}, (e) => {
uni.hideLoading();
console.log(e.msg ? e.msg : '获取文件信息失败');
uni.showToast({
title: e.msg ? e.msg : '获取文件信息失败',
icon: "none"
})
this.disabled = false;
});
传参
property |
description |
path |
必传,视频文件的完整路径,通过uni-file-picker等组件选择视频后会获取到文件路径,通过使用plus.io.convertLocalFileSystemURL(path)转换为完整的路径作为参数 |
回调参数
property |
description |
path |
视频文件所在完整路径 |
title |
压缩完成的视频文件的路径,默认为应用沙盒内 |
author |
作者 |
duration |
视频时长(毫秒) |
filesize |
文件大小(位),如果要格式化为MB,可以除1000*1000 |
width |
宽度 |
height |
高度 |
orientation |
视频方向 |
获取视频帧截图base64
如果使用uni.previewImage预览base64图片,可能会导致闪退(传递的数据过大),解决方法是将base64存储到本地,得到一个图片路径,根据这个路径预览图片;
let appendixPath = this.filePathList[index ? index : 0];
let params = {
position: position,
path: plus.io.convertLocalFileSystemURL(appendixPath), //绝对路径
quality: 100
}
videoCompressModule.getThumbnailBase64(params, (res) => {
console.log("res:", res)
uni.hideLoading();
if (!res) {
return;
}
this.imageData = res.frameBase64;
this.imageData = this.imageData.replace(/[\r\n]/g, "");
this.imageData = `data:image/jpg;base64,${this.imageData}`;
}, (e) => {
uni.hideLoading();
uni.showToast({
title: e ? e.msg : '获取视频帧失败',
icon: "none"
})
});
传参
property |
description |
position |
非必传,视频帧的位置,单位是秒 |
path |
必传,视频文件的完整路径,通过uni-file-picker等组件选择视频后会获取到文件路径,通过使用plus.io.convertLocalFileSystemURL(path)转换为完整的路径作为参数 |
quality |
非必传,图片质量,默认为100,范围为0-100,值越大质量越高 |
回调参数
property |
description |
frameBase64 |
视频帧的base64字符串,拼接上data:image/jpg;base64, 可以直接用于image显示 |
获取视频帧截图保存到文件并返回文件路径
uni.showLoading({
title: '操作中..'
});
let appendixPath = this.filePathList[index ? index : 0];
let params = {
position: position,
path: plus.io.convertLocalFileSystemURL(appendixPath), //绝对路径
quality: 100
}
console.log(params)
videoCompressModule.getThumbnailPath(params, (res) => {
uni.hideLoading();
console.log(res.filePath);
this.frameImagePath = res.filePath;
}, (e) => {
uni.hideLoading();
uni.showToast({
title: e ? e.msg : '获取视频帧失败',
icon: "none"
})
});
传参
property |
description |
position |
非必传,视频帧的位置,单位是秒 |
path |
必传,视频文件的完整路径,通过uni-file-picker等组件选择视频后会获取到文件路径,通过使用plus.io.convertLocalFileSystemURL(path)转换为完整的路径作为参数 |
quality |
非必传,图片质量,默认为100,范围为0-100,值越大质量越高 |
回调参数
property |
description |
filePath |
视频帧图片文件保存的完整路径 |
删除视频文件
let appendixPath = this.filePathList[index ? index : 0];
videoCompressModule.deleteVideo({
//输入输出
path: plus.io.convertLocalFileSystemURL(appendixPath), //绝对路径
}, (res) => {
uni.showToast({
title: res ? res : '删除成功'
})
this.reset();
}, (e) => {
uni.showToast({
title: e ? e.msg : '删除失败',
icon: "none"
})
});
传参
property |
description |
path |
必传,视频文件的完整路径,通过uni-file-picker等组件选择视频后会获取到文件路径,通过使用plus.io.convertLocalFileSystemURL(path)转换为完整的路径作为参数 |
回调参数
无
完整示例
<template>
<view class="content">
<view class="picker-wrapper">
<uni-file-picker
ref="files"
v-model='fileList'
:auto-upload="false"
:disabled='disabled'
:limit="1"
file-mediatype='video'
@select='handleSelect'
/>
</view>
<view class="operation-layout">
<button class="operation-btn" @click="getThumbnailBase64(0,positionBase64)">获取帧截图base64字符串</button>
<view class="layout-horizontal">
<text>截图位置(秒):</text>
<slider :value="positionBase64" @change="sliderChangeBase64" min="0" :max="videoDuration" step="0.1"
show-value/>
</view>
<view class="layout-horizontal">
<text>截图质量:</text>
<slider :value="qualityBase64Snapshot" @change="sliderChangeBase64SnapshotQuality" min="0" :max="100" step="1"
show-value/>
</view>
<view style="width: 100%;margin-top: 40rpx" v-if="imageData">
<text>帧截图base64字符串:</text>
<view style="width: 100%;background: black;display: flex;flex-direction: column"
@click="previewImage([imageData])">
<image style="width:100%;height: 200px;" mode="aspectFit" :src="imageData"></image>
</view>
</view>
</view>
<view class="operation-layout">
<button class="operation-btn" @click="getThumbnailPath(0,positionFramePath)">获取帧截图文件路径</button>
<view class="layout-horizontal">
<text>截图位置(秒):</text>
<slider :value="positionFramePath" @change="sliderChangeFramePath" min="0" :max="videoDuration" step="0.1"
show-value/>
</view>
<view class="layout-horizontal">
<text>截图质量:</text>
<slider :value="qualityFramePathSnapshot" @change="sliderChangeFramePathSnapshotQuality" min="0" :max="100"
step="1"
show-value/>
</view>
<view class="text-layout" v-if="frameImagePath">
<text>视频帧截图文件路径:</text>
<text>{{ frameImagePath ? `\n${frameImagePath}` : '' }}</text>
<view style="width: 100%;background: black;display: flex;flex-direction: column"
@click="previewImage([frameImageUrl])">
<image id="framePathImage" key="framePathImage" style="width:100%;height: 200px;" mode="aspectFit"
:src="frameImageUrl"></image>
</view>
</view>
</view>
<view class="operation-layout">
<button class="operation-btn" @click="compress(0)">压缩视频</button>
<view class="layout-horizontal">
<text>压缩质量:</text>
<slider :value="qualityCompress" @change="sliderChangeCompressQuality" min="1" max="7" step="1" show-value/>
</view>
<view class="layout-horizontal">
<text>开始时间(秒):</text>
<slider :value="startTime" @change="sliderChangeStartTime" min="0" :max="videoDuration" step="1" show-value/>
</view>
<view class="layout-horizontal">
<text>时长(秒):</text>
<slider :value="duration" @change="sliderChangeDuration" min="0" :max="videoDuration" step="0.1" show-value/>
</view>
<view class="layout-horizontal">
<text>是否包含音频:</text>
<radio-group @change="radioChange" style="margin-left: 15px;flex: 1;line-height:40px;margin-bottom: 10px;">
<view class="layout-horizontal">
<view class="layout-horizontal-pure" v-for="(item, index) in items" :key="item.value">
<view>
<radio :value="item.value" :checked="index === current"/>
</view>
<view>{{ item.name }}</view>
</view>
</view>
</radio-group>
</view>
<view style="width: 100%;margin-top: 40rpx" v-if="compressedVideoInfo.path">
<text>压缩后视频:</text>
<text>{{ compressedVideoInfo.path ? `\n${JSON.stringify(compressedVideoInfo)}` : '' }}</text>
<view style="width: 100%;height: 200px;background: black;display: flex;flex-direction: column">
<video style="width: 100%;" :src="compressedVideoUrl" :http-cache="false"></video>
</view>
</view>
</view>
<view class="operation-layout">
<button class="operation-btn" @click="getMediaInfo">获取视频信息</button>
<view style="width: 100%;" v-if="videoInfo.path">
<text>原视频:</text>
<text>{{ videoInfo.path ? `\n${JSON.stringify(videoInfo)}` : '' }}</text>
<view style="width: 100%;height: 200px;background: black;display: flex;flex-direction: column">
<video style="width: 100%;" :src="sourceVideoUrl"></video>
</view>
</view>
</view>
<view class="operation-layout">
<button class="operation-btn" @click="cancelCompression">取消压缩</button>
</view>
<view class="operation-layout">
<button class="operation-btn" @click="deleteVideo(0)">删除视频文件</button>
</view>
<view class="operation-layout">
<button class="operation-btn" @click="reset">重置</button>
</view>
</view>
</template>
<script>
// #ifdef APP-PLUS
var videoCompressModule = uni.requireNativePlugin('VideoCompressUniPlugin-VideoCompressModule');
// #endif
export default {
data() {
return {
title: '视频压缩示例',
// 视频帧base64
imageData: undefined,
// 是否禁用
disabled: false,
// 选中的文件列表
fileList: [],
// 文件路径列表
filePathList: [],
// 视频文件信息
videoInfo: {},
// 视频文件信息
compressedVideoInfo: {},
// 视频帧图片文件路径
frameImagePath: undefined,
// 视频帧位置
positionBase64: 0,
// 视频帧质量
qualityBase64Snapshot: 100,
// 视频帧位置
positionFramePath: 0,
// 视频帧质量
qualityFramePathSnapshot: 100,
// 视频压缩质量
qualityCompress: 2,
// 是否包含音频数据选项
items: [{
value: '1',
name: '包含',
checked: 'true'
},
{
value: '0',
name: '不包含'
},
],
// 当前勾选的是否包含音频
current: 0,
// 开始时间
startTime: 0,
// 持续时间
duration: 0,
}
},
computed: {
// 将平台绝对路径转换为本地URL路径
sourceVideoUrl() {
if (this.videoInfo.path) {
console.log('sourceVideoUrl', this.videoInfo.path)
return `file://${this.videoInfo.path}`;
}
return undefined;
},
// 将平台绝对路径转换为本地URL路径
compressedVideoUrl() {
if (this.compressedVideoInfo.path) {
console.log('compressedVideoUrl', this.compressedVideoInfo.path)
return `file://${this.compressedVideoInfo.path}`;
}
return undefined;
},
// 将平台绝对路径转换为本地URL路径
frameImageUrl() {
if (this.frameImagePath) {
return `file://${this.frameImagePath}`;
}
return undefined;
},
// 视频最大的位置
videoDuration() {
if (this.videoInfo.duration) {
return parseFloat((this.videoInfo.duration / 1000).toFixed(1));
}
return 0;
},
},
onLoad() {
},
methods: {
radioChange: function (evt) {
for (let i = 0; i < this.items.length; i++) {
if (this.items[i].value === evt.detail.value) {
this.current = i;
break;
}
}
},
// base64位置改变
sliderChangeBase64(e) {
this.positionBase64 = e.detail.value;
},
// frame path位置改变
sliderChangeFramePath(e) {
this.positionFramePath = e.detail.value;
},
// frame path 截图质量改变
sliderChangeBase64SnapshotQuality(e) {
this.qualityBase64Snapshot = e.detail.value;
},
// frame path 截图质量改变
sliderChangeFramePathSnapshotQuality(e) {
this.qualityFramePathSnapshot = e.detail.value;
},
// 压缩质量改变
sliderChangeCompressQuality(e) {
this.qualityCompress = e.detail.value;
},
// 开始时间
sliderChangeStartTime(e) {
this.startTime = e.detail.value;
},
// 持续时间
sliderChangeDuration(e) {
this.duration = e.detail.value;
},
// // 预览图片
// previewImageBase64(base64) {
// // 预览图片
// uni.previewImage({
// urls: urls,
// longPressActions: {
// itemList: ['发送给朋友', '保存图片', '收藏'],
// success: function (data) {
// console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
// },
// fail: function (err) {
// console.log(err.errMsg);
// }
// }
// });
// },
// 预览图片
previewImage(urls) {
// 预览图片
uni.previewImage({
urls: urls,
longPressActions: {
itemList: ['发送给朋友', '保存图片', '收藏'],
success: function (data) {
console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
},
fail: function (err) {
console.log(err.errMsg);
}
}
});
},
// 选择文件后
handleSelect(e) {
console.log(this.fileList)
console.log("e11111", this.fileList)
this.filePathList = e.tempFilePaths;
uni.showToast({
icon: 'none',
title: this.filePathList,
duration: 6000,
});
this.disabled = false;
this.getMediaInfo();
},
// 获取视频帧base64字符串
getThumbnailBase64(index, position) {
uni.showLoading({
title: '操作中..'
});
let appendixPath = this.filePathList[index ? index : 0];
this.imageData = undefined;
let params = {
position: this.positionBase64,
path: plus.io.convertLocalFileSystemURL(appendixPath), //绝对路径
quality: this.qualityBase64Snapshot
}
console.log(params)
videoCompressModule.getThumbnailBase64(params, (res) => {
console.log("res:", res)
uni.hideLoading();
if (!res) {
return;
}
this.imageData = res.frameBase64;
this.imageData = this.imageData.replace(/[\r\n]/g, "");
this.imageData = `data:image/jpg;base64,${this.imageData}`;
}, (e) => {
uni.hideLoading();
uni.showToast({
title: e ? e.msg : '获取视频帧失败',
icon: "none"
})
});
},
// 获取视频帧文件路径
getThumbnailPath(index, position) {
uni.showLoading({
title: '操作中..'
});
this.frameImagePath = undefined;
let appendixPath = this.filePathList[index ? index : 0];
let params = {
position: this.positionFramePath,
path: plus.io.convertLocalFileSystemURL(appendixPath), //绝对路径
quality: this.qualityFramePathSnapshot
}
console.log(params)
videoCompressModule.getThumbnailPath(params, (res) => {
uni.hideLoading();
console.log(res.filePath);
this.frameImagePath = res.filePath;
}, (e) => {
uni.hideLoading();
uni.showToast({
title: e ? e.msg : '获取视频帧失败',
icon: "none"
})
});
},
// 删除视频文件
deleteVideo(index) {
let appendixPath = this.filePathList[index ? index : 0];
videoCompressModule.deleteVideo({
//输入输出
path: plus.io.convertLocalFileSystemURL(appendixPath), //绝对路径
}, (res) => {
uni.showToast({
title: res ? res : '删除成功'
})
this.reset();
}, (e) => {
uni.showToast({
title: e ? e.msg : '删除失败',
icon: "none"
})
});
},
// 重置数据
reset() {
this.fileList = [];
this.filePathList = [];
this.videoInfo = {};
this.compressedVideoInfo = {};
this.imageData = undefined;
this.frameImagePath = undefined;
// 视频帧位置
this.positionBase64 = 0;
// 视频帧质量
this.qualityBase64Snapshot = 100;
// 视频帧位置
this.positionFramePath = 0;
// 视频帧质量
this.qualityFramePathSnapshot = 100;
// 视频压缩质量
this.qualityCompress = 2;
// 当前勾选的是否包含音频
this.current = 0;
// 开始时间
this.startTime = 0;
// 持续时间
this.duration = 0;
},
// 取消压缩
cancelCompression() {
videoCompressModule.cancelCompression({}, (res) => {
uni.showToast({
title: res ? res : '取消成功',
})
}, (e) => {
uni.showToast({
title: e ? e.msg : '取消失败',
icon: "none"
})
});
},
// 压缩视频
compress(index) {
uni.showLoading({
title: '开始压缩'
});
this.compressedVideoInfo = {};
let appendixPath = this.filePathList[index ? index : 0];
let params = {
//输入输出
inPath: plus.io.convertLocalFileSystemURL(appendixPath), //绝对路径
// 视频压缩质量
quality: this.qualityCompress,
// 开始时间
startTime: this.startTime,
// 持续时间
duration: this.duration,
// 是否包含音频
includeAudio: this.items[this.current].value === '1',
}
console.log('params:', params)
videoCompressModule.compress(params, (res) => {
//done标识完成
if (res.done) {
uni.hideLoading();
this.getInfo(true, res.outPath).then(info => {
this.compressedVideoInfo = info;
})
}
}, (e) => {
uni.hideLoading();
uni.showToast({
title: e.msg ? e.msg : '压缩失败',
icon: "none"
})
});
},
// 获取源视频信息
getMediaInfo() {
this.getInfo(false, this.filePathList[0]).then(info => {
this.videoInfo = info;
this.duration = parseFloat((info.duration / 1000).toFixed(1));
})
},
// 获取文件信息
getInfo(converted, path) {
return new Promise((resolve, reject) => {
uni.showLoading({
title: '操作中..'
});
// path:视频压缩后的路径
videoCompressModule.getMediaInfo({
path: converted ? path : plus.io.convertLocalFileSystemURL(path) //本地视频绝对路径
}, (res) => {
uni.hideLoading();
console.log(res);
resolve(res);
}, (e) => {
uni.hideLoading();
console.log(e.msg ? e.msg : '获取文件信息失败');
uni.showToast({
title: e.msg ? e.msg : '获取文件信息失败',
icon: "none"
})
this.disabled = false;
reject();
});
});
},
}
}
</script>
<style lang="scss">
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 20px;
overflow-y: scroll;
overflow-x: hidden;
}
.operation-layout {
width: 100%;
display: flex;
margin-top: 20px;
box-sizing: border-box;
padding: 10px 15px;
flex-direction: column;
border: 1px solid #DEDEDE;
.layout-horizontal {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
margin-top: 10px;
slider {
flex: 1;
}
}
.layout-horizontal-pure {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
}
.operation-btn {
width: 80%;
}
.operation-btn + .operation-btn {
margin-top: 15px;
}
}
.text-layout {
width: 100%;
overflow-y: scroll;
margin-top: 20px;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 18px;
color: #8f8f94;
}
.picker-wrapper {
width: 100%;
margin-top: 20px;
}
text {
font-size: 16px;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 600;
color: #333333;
line-height: 22px;
white-space: pre-line;
display: block;
text-overflow: ellipsis;
word-wrap: break-word;
}
</style>