基于uniapp 使用 live-pusher 实现 拍照前添加上次拍照图片 并将快照转base64格式上传

  • 准备
  • 开始
  • camera.nvue
  • 特别注意
  • 上传页
  • 处理



最新项目上有一个记录某处工程进度的功能 ,需要定时去拍照,拍照时需要将上一次拍摄的照片放在拍照界面当做参考,查阅了挺多资料 大多都是基于 live-pusher 实现 所以下面就记录一下整个的流程

准备

首先 你要先在你的manifest.json文件勾选上这一项功能 确保你的功能能够正常调用

uniapp使用ios上传图片_uni-app


这两串是为了你的快照保存到本地之前 能获取你的存储权限(我项目的功能不需要保存到本地,但之前翻阅文档需要添加,就没删除,加上可能保险一点吧)

uniapp使用ios上传图片_uniapp使用ios上传图片_02


这里也有更详细的获取存储权限的处理方法 具体的看自己的项目功能需不需要加

uniapp使用ios上传图片_上传_03

开始

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 组件 但原来的组件值仅仅支持相机和本地 所以需要自己稍微修改一下 加一下‘水印拍照’的选项

uniapp使用ios上传图片_ide_04


uniapp使用ios上传图片_ide_05


uniapp使用ios上传图片_uniapp使用ios上传图片_06

这里需要注意的地方就一个 背景图片的相关操作尽量都在这边处理好在传过去

<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>

uniapp使用ios上传图片_宽高_07

处理

水印加上之后就要处理拍照结果了 点击拍照之后确认提交调用原表单页的处理方法

<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>