基于uniapp 使用 live-pusher 实现 拍照前添加上次拍照图片 并将快照转base64格式上传
- 准备
- 开始
- camera.nvue
- 特别注意
- 上传页
- 处理
最新项目上有一个记录某处工程进度的功能 ,需要定时去拍照,拍照时需要将上一次拍摄的照片放在拍照界面当做参考,查阅了挺多资料 大多都是基于 live-pusher 实现 所以下面就记录一下整个的流程
准备
首先 你要先在你的manifest.json文件勾选上这一项功能 确保你的功能能够正常调用
这两串是为了你的快照保存到本地之前 能获取你的存储权限(我项目的功能不需要保存到本地,但之前翻阅文档需要添加,就没删除,加上可能保险一点吧)
这里也有更详细的获取存储权限的处理方法 具体的看自己的项目功能需不需要加
开始
camera.nvue
首先 你的应用页面一定要是 *.nvue文件 要不然你的代码不会生效
<template>
<view class="pengke-camera" :style="{ width: `${windowWidth}px`, height: `${windowHeight}px` }">
<view class="preview" :style="{ width: `${windowWidth}px`, height: `${windowHeight-90}px` }">
<live-pusher id="livePusher" ref="livePusher" class="livePusher" mode="FHD" beauty="0" whiteness="0"
:aspect="aspect" min-bitrate="1000" audio-quality="16KHz" device-position="back" :auto-focus="true"
:muted="true" :enable-camera="true" :enable-mic="false" :zoom="false" @statechange="statechange"
:style="{ width: `${windowWidth}px`, height: `${!!snapshotsrc ? 1 : windowHeight - 90}px` }">
</live-pusher>
<!-- 横向 -->
<view v-if="showBG && ratio>1 && !!!snapshotsrc && showSna" class="bg_box"
:style="{ width: `${windowHeight-90}px`, height: `${windowWidth}px`}">
<image :style="{width:`${ windowHeight-90}px`, height:`${windowWidth}px`, opacity:`${sliderValue/100}`}"
class="img_bg" :src="BG">
</image>
</view>
<!-- 竖向 -->
<view v-if="showBG && ratio<=1 && !!!snapshotsrc && showSna" class="bg_box2"
:style="{ width: `${windowWidth}px`, height: `${windowHeight-90}px`}">
<image :style="{ width:`${windowWidth}px`, height:`${windowHeight-90}px`, opacity:`${sliderValue/100}`}"
class="img_bg2" :src="BG">
</image>
</view>
<image :src="snapshotsrc" :style="{ width: `${windowWidth}px`, height: `${windowHeight-90}px` }"
v-if="!!snapshotsrc "></image>
<view class="u_slider" v-if="showBG && !!!snapshotsrc && showSna"
:style="{ width: `${30}px`,left: `${windowWidth-30}px`}">
<!-- 透明度: -->
<span class="u_sliderJia" @click="changeNum(1)">↑</span>
<span class="u_sliderNum">{{sliderValue}}</span>
<span class="u_sliderJia" @click="changeNum(2)">↓</span>
</view>
</view>
<view class="menu">
<!--返回键-->
<view class="menu-back" @tap="back" v-if="!!!snapshotsrc">返回</view>
<!--快门键-->
<view class="cameraBtn" @click="snapshot" @tap="snapshot" v-if="!!!snapshotsrc">拍照
</view>
<!--反转键-->
<!-- <view class="menu-flip" @tap="flip" v-if="!!!snapshotsrc">反转 </view> -->
<!--重拍-->
<view class="menu-back" @click="snapshotsrc = null" v-if="!!snapshotsrc">重拍 </view>
<!--确认-->
<view class="menu-flip" @click="urlOk" v-if="!!snapshotsrc">确认 </view>
<!--显示隐藏水印-->
<view class="menu-flip" @click="showSna = !showSna" v-if="showBG && !!!snapshotsrc">
{{!showSna?'显示':'隐藏'}}
</view>
</view>
</view>
</template>
<script>
let _this = null;
export default {
data() {
return {
poenCarmeInterval: null, //打开相机的轮询
aspect: '2:3', //比例
windowWidth: '', //屏幕可用宽度
windowHeight: '', //屏幕可用高度
camerastate: false, //相机准备好了
livePusher: null, //流视频对象
snapshotsrc: null, //快照
showSna: false,
BG: null, // 上一次拍的照片 直接传进来
ratio: 1, // 根据你照片宽高比例 控制 横向/竖向 显示水印
showBG: false, // 是否要显示 显示水印按钮
sliderValue: 30, // 水印 透明度
};
},
onLoad(e) {
_this = this;
this.initCamera();
this.allInfo = JSON.parse(e.config);
this.BG = this.allInfo.data
this.ratio = this.allInfo.ratio
this.showBG = this.allInfo.showBG
},
onReady() {
this.livePusher = uni.createLivePusherContext('livePusher', this);
this.startPreview(); //开启预览并设置摄像头
this.poenCarme();
},
methods: {
//轮询打开
poenCarme() {
//#ifdef APP-PLUS
if (plus.os.name == 'Android') {
this.poenCarmeInterval = setInterval(function() {
// console.log(_this.camerastate);
if (!_this.camerastate) _this.startPreview();
}, 2500);
}
//#endif
},
//初始化相机
initCamera() {
uni.getSystemInfo({
success: function(res) {
_this.windowWidth = res.windowWidth;
_this.windowHeight = res.windowHeight;
let zcs = _this.aliquot(_this.windowWidth, _this.windowHeight);
_this.aspect = (_this.windowWidth / zcs) + ':' + (_this.windowHeight / zcs);
// console.log('画面比例:'+_this.aspect);
}
});
},
//整除数计算
aliquot(x, y) {
if (x % y == 0) return y;
return this.aliquot(y, x % y);
},
//开始预览
startPreview() {
this.livePusher.startPreview({
success: a => {
// console.log(a)
}
});
},
//停止预览
stopPreview() {
this.livePusher.stopPreview({
success: a => {
_this.camerastate = false;
}
});
},
//状态
statechange(e) {
//状态改变
console.log(e);
if (e.detail.code == 1007) {
_this.camerastate = true;
} else if (e.detail.code == -1301) {
_this.camerastate = false;
}
},
//返回
back() {
uni.navigateBack();
},
urlOk() {
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2];
prevPage.$vm.getTheImg(_this.snapshotsrc) // 将获取的快照地址传回上一个页面
uni.navigateBack();
},
// 进度条
changeNum(val) {
if (val == 1) {
this.sliderValue += 10
} else {
this.sliderValue -= 10
}
if (this.sliderValue <= 10) this.sliderValue = 10
if (this.sliderValue >= 100) this.sliderValue = 90
},
//抓拍
snapshot() {
//震动
uni.vibrateShort({
success: function() {
console.log('success');
}
});
//拍照
this.livePusher.snapshot({
success: e => {
_this.snapshotsrc = e.message.tempImagePath;
console.log('抓拍', e, e.message.tempImagePath);
// 打印结果 仅供参考
//{
// "message": {
// "height": 1857,
// "width": 1080,
// "tempImagePath": "/storage/emulated/0/Android/data/io.dcloud.HBuilder/apps/HBuilder/doc/snapshot/snapshot_1705631842226.jpg"
// },
// "errMsg": "snapshot:ok"
//}
// 原本是想在这里做文件上传 但真机一直运行不了 所以后面直接将数据传回表单页 在表单页做上传处理 具体看 urlOk()
// uni.getImageInfo({
// src: _this.snapshotsrc,
// success: function(image) {
// uni.downloadFile({
// url: image.tempImagePath,
// success: (res) => {
// // 下载成功
// console.log('下载成功', res);
// if (res.statusCode === 200) {
// // res.tempFilePath 为下载后的临时文件路径
// const tempFilePath = res.tempFilePath;
// // 在这里可以对 tempFilePath 进行处理,例如上传到服务器或者保存到本地
// // 示例:保存快照到相册
// uni.saveImageToPhotosAlbum({
// filePath: tempFilePath,
// success: () => {
// uni.showToast({
// title: '快照保存成功',
// icon: 'success'
// });
// },
// fail: () => {
// uni.showToast({
// title: '快照保存失败',
// icon: 'none'
// });
// }
// });
// }
// },
// fail: (e) => {
// console.log(e)
// uni.showToast({
// title: '快照下载失败',
// icon: 'none'
// });
// }
// });
// }
// })
}
});
},
/**
* 获取临时路径的图片宽高大小
* **/
getImageInfo(path) {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: path,
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(err);
resolve(err);
},
});
});
},
async uploadImage(path) {
console.log('uploadImage', path);
let info = await this.getImageInfo(path); //获取临时路径的图片宽高大小
let width = Math.round(
((this.rpx2px * this.finder.width) / this.cameraWidth) * info.width
),
height = Math.round(
((this.rpx2px * this.finder.height) / (this.windowHeight - 90)) *
info.height
);
let x = parseInt((info.width - width) / 2),
y = parseInt((info.height - height) / 2);
uploadFileOSS(path)
.then((res) => {
this.snapshotsrc =
res.url +
`?x-oss-process=image/crop,x_${x},y_${y},w_${width},h_${height}/rotate,270`;
console.log(this.snapshotsrc);
this.setImage({
x,
y,
width,
height
});
uni.navigateBack({
delta: 2,
});
})
.catch((err) => {
console.log(err);
errorMsg(err);
});
},
//反转
flip() {
this.livePusher.switchCamera();
},
//设置
setImage() {
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2];
prevPage.$vm.setImage({
path: _this.snapshotsrc
});
}
}
};
</script>
<style>
/* .nvue文件需要单独加上改处理方法 不然无法编译打包*/
/* #ifdef APP-PLUS-NVUE */
.pengke-camera {}
.preview {
position: relative;
z-index: 1;
}
.livePusher {
position: relative;
z-index: 20;
}
.bg_box {
position: absolute;
top: 125%;
left: -125%;
transform: rotate(90deg);
}
.bg_box2 {
position: absolute;
top: 0;
left: 0;
}
.img_bg2 {
position: absolute;
}
.img_bg {
position: absolute;
}
.u_slider {
position: relative;
top: -90px;
/* left: 0; */
/* width: ; */
height: 90px;
z-index: 99;
border-radius: 20px 0 0 20px;
background-color: #ffffff;
}
.u_sliderJia {
/* display: inline-block; */ /* .nvue文件不支持该写法*/
width: 30px;
height: 30px;
border-radius: 100%;
padding: 5px;
font-size: 24px;
text-align: center;
background-color: #fff;
}
.u_sliderNum {
/* display: inline-block; */ /* .nvue文件不支持该写法*/
width: 30px;
height: 30px;
padding: 5px 0px;
line-height: 30px;
}
.menu {
position: absolute;
left: 0;
bottom: 0;
width: 750rpx;
height: 180rpx;
z-index: 98;
align-items: center;
justify-content: center;
}
.menu-mask {
position: absolute;
left: 0;
bottom: 0;
width: 750rpx;
height: 180rpx;
z-index: 98;
}
.menu-back {
position: absolute;
left: 30rpx;
bottom: 50rpx;
width: 80rpx;
height: 80rpx;
z-index: 99;
align-items: center;
justify-content: center;
}
.menu-snapshot {
width: 130rpx;
height: 130rpx;
z-index: 99;
}
.menu-flip {
position: absolute;
right: 30rpx;
bottom: 50rpx;
width: 80rpx;
height: 80rpx;
z-index: 99;
align-items: center;
justify-content: center;
}
/* #endif */
</style>
特别注意
.nvue 文件很多原本的css样式不支持 需要在不支持的地方加上这样的处理
<style>
/* .nvue文件需要单独加上改处理方法 不然无法编译打包*/
/* #ifdef APP-PLUS-NVUE */
uni-modal .uni-modal__bd {
text-align: left !important;
white-space: pre-line !important;
}
/* #endif */
</style>
上传页
照片的拍照页是用的 u-upload 组件 但原来的组件值仅仅支持相机和本地 所以需要自己稍微修改一下 加一下‘水印拍照’的选项
这里需要注意的地方就一个 背景图片的相关操作尽量都在这边处理好在传过去
<script>
export default {
methods: {
selectClick(index) {
const _this = this
let itemType = this.listSheet[index].itemType
if (itemType == 'other') {
let config = {
data: '',
ratio: 1,
}
if (!!_this.BGImg) {
// 需要展示水印图片
// 获取图片信息
uni.getImageInfo({
src: _this.BGImg,
success: function (image) {
const _ratio = image.width / image.height
config = {
showBG: true, // 是否需要显示 显示水印 按钮
data: _this.BGImg, // 背景图片
ratio: _ratio // 宽高比
}
uni.navigateTo({
// 跳转至你的组件页面
url: '/pages/hse/photo/photoCS/camera?config=' + JSON.stringify(config),
})
}
});
} else {
//不需要展示水印图片
config = {
showBG: false,
data: '',
ratio: 1
}
uni.navigateTo({
url: '/pages/hse/photo/photoCS/camera?config=' + JSON.stringify(config),
})
}
} else if (itemType == 'album') {
this.selectFile(['album']) // 直接打开相册选择 原本u-upload的方法 直接用 不处理
} else {
this.selectFile(['camera']) // 直接打卡原生摄像头 原本u-upload的方法 直接用 不处理
}
},
}
}
</script>
处理
水印加上之后就要处理拍照结果了 点击拍照之后确认提交调用原表单页的处理方法
<script>
export default {
methods: {
// .nvue 页面的方法
urlOk() {
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2];
prevPage.$vm.getTheImg(_this.snapshotsrc) // 触发表单页方法
uni.navigateBack();
},
}
}
</script>
<script>
export default {
methods: {
// form 表单页的方法
async getTheImg(path) {
let _this = this
const _name = path.substring(path.lastIndexOf("/") + 1)
// 可以用uniapp的 plus.zip.compressImage() 方法处理临时图片地址 然后转base64 上传至服务器
plus.zip.compressImage({
src: path,//src: 压缩原始图片的路径
dst: path,
overwrite: true,
quality: 40
},
zipRes => {
setTimeout(() => {
var reader = new plus.io.FileReader();
reader.onloadend = res => {
//获取图片base64
var speech = res.target.result; //base64图片
console.log('base64 11111', speech)
request({
url: '**********',
method: "post",
data: {
dataUrl: speech,
},
}).then((res) => {
console.log(res); // 这里就可以直接处理上传后的数据了
})
}
//一定要使用plus.io.convertLocalFileSystemURL将target地址转换为本地文件地址,否则readAsDataURL会找不到文件
reader.readAsDataURL(plus.io.convertLocalFileSystemURL(zipRes.target));
}, 100);
},
function (error) {
console.log('Compress error!', error);
}
);
},
}
</script>