uniapp采用直播推流组件live-pusher 实现扫码,保存图片。

使用UniApp中的live-pusher组件实现摄像头实时预览功能,通过调用plus.barcode接口进行二维码识别。同时利用plus.io.DirectoryEntry接口实现图片的删除和转换操作。

代码

<template>
	<view class="content">
		<view class="camera-container">
			<live-pusher v-if="showLivePusher" id="livePusher" ref="livePusher" class="live-pusher"
				url="" mode="FHD" :beauty="0" :whiteness="0" aspect="9:16" muted="true"
				:device-position="devicePosition" :auto-focus="true" @statechange="statechange" @netstatus="netstatus"
				@error="error"></live-pusher>
			<view v-else class="permission-hint" @click="checkAndStartPreview">
				<text class="permission-text">点击开启摄像头预览</text>
			</view>
		</view>

		<view class="controls">
			<button class="btn" type="primary" @click="takePhoto"><text class="btn-text">拍照试试</text></button>
			<button class="btn" @click="switchCamera"><text class="btn-text">切换镜头</text></button>
			<button class="btn" @click="startPreview"><text class="btn-text">开启预览</text></button>
			<button class="btn" @click="takePhotoloop"><text class="btn-text">连续扫描</text></button>
			<button class="btn" @click="stopPreview"><text class="btn-text">停止预览</text></button>
		</view>
		<view class="preview-box" v-if="snapshotPath">
			<text class="preview-text">拍摄结果:</text>
			<image class="preview-img" :src="snapshotPath" mode="aspectFit"></image>
		</view>
		
		
	</view>
</template>

<script>
	
	export default {
		data() {
			return {
				snapshotPath: '',
				pusherContext: null,
				showLivePusher: false,
				takePhotoLoop: false,
				devicePosition: 'back', // 'front' 为前置,'back' 为后置
				
			}
		},
		onLoad() {
			// 页面加载时尝试检查权限
			this.checkAndStartPreview();
		},
		methods: {
			async checkAndStartPreview() {
				// #ifdef APP-PLUS
				const status = await this.checkPermission();
				if (status) {
					this.initPusher();
				} else {
					uni.showModal({
						title: '提示',
						content: '需要摄像头权限才能拍照,请在设置中打开',
						confirmText: "去设置",
						success: (res) => {
							if (res.confirm) {
								plus.runtime.openURL("app-settings:");
							}
						}
					});
				}
				// #endif

				// #ifndef APP-PLUS
				this.initPusher();
				// #endif
			},
			initPusher() {
				this.showLivePusher = true;
				// NVUE 中通常不需要太长的 nextTick 等待,但为了稳妥保留一点延迟
				setTimeout(() => {
					// 确保上下文存在
					if (!this.pusherContext) {
						this.pusherContext = uni.createLivePusherContext('livePusher', this);
					}
					console.log("Context created, starting preview...");
					this.startPreview();
				}, 300);
			},
			checkPermission() {
				return new Promise((resolve) => {
					if (uni.getSystemInfoSync().platform == 'android') {
						plus.android.requestPermissions(['android.permission.CAMERA'], (e) => {
							if (e.granted.length > 0) {
								resolve(true);
							} else {
								resolve(false);
							}
						}, (e) => {
							resolve(false);
						});
					} else {
						// iOS
						const AVCaptureDevice = plus.ios.importClass("AVCaptureDevice");
						const status = AVCaptureDevice.authorizationStatusForMediaType("vide");
						if (status === 3) { // authorized
							resolve(true);
						} else {
							AVCaptureDevice.requestAccessForMediaType_completionHandler("vide", (granted) => {
								resolve(granted);
							});
						}
					}
				});
			},
			startPreview() {
				if (!this.pusherContext) return;
				console.log("尝试开启预览...");
				this.pusherContext.startPreview({
					success: (a) => {
						console.log("livePusher.startPreview success:" + JSON.stringify(a));
					},
					fail: (err) => {
						console.error("livePusher.startPreview fail:" + JSON.stringify(err));
						uni.showToast({
							title: '预览失败: ' + (err.errMsg || JSON.stringify(err)),
							icon: 'none',
							duration: 3000
						});
					}
				});
			},
			stopPreview() {
				console.log("尝试停止预览...");
				this.takePhotoLoop = false;
				if (!this.pusherContext) return;
				this.pusherContext.stopPreview({
					success: (a) => {
						console.log("livePusher.stopPreview:" + JSON.stringify(a));
					}
				});
			},
			switchCamera() {
				if (!this.pusherContext) return;
				this.pusherContext.switchCamera({
					success: (e) => {
						console.log("switchCamera success:" + JSON.stringify(e));
						uni.showToast({
							title: '切换成功',
							icon: 'none'
						});
					},
					fail: (err) => {
						console.error("switchCamera fail:" + JSON.stringify(err));
						uni.showToast({
							title: '切换失败',
							icon: 'none'
						});
					}
				});
			},
			takePhotoloop(){
				this.takePhotoLoop =true
				this.takePhoto()
			},
			takePhoto() {
				if (!this.pusherContext) {
					uni.showToast({
						title: '相机未启动',
						icon: 'none'
					});
					return;
				}
				this.pusherContext.snapshot({
					success: async (e) => {
						console.log("snapshot success:" + JSON.stringify(e));
						this.snapshotPath = e.message.tempImagePath;
						// 显示 canvas 供绘制
						
						await this.$nextTick();
						// 解析二维码
						let qrcodeData = await this.decodeQR(e.message);
						console.log('decodeQR结果', qrcodeData);
						if (qrcodeData) {
							uni.showToast({ title: '二维码:' + qrcodeData, icon: 'none', duration: 3000 });
							this.deleteFile(e.message.tempImagePath);
						} else {
							this.deleteFile(e.message.tempImagePath);
							uni.showToast({ title: '未识别到二维码', icon: 'none' });
						}
						if(this.takePhotoLoop){
							setTimeout(() => {
								this.takePhoto()
							}, 300);
						}
						
					},
					fail: (err) => {
						console.error("snapshot fail:" + JSON.stringify(err));
						uni.showToast({
							title: '拍照失败: ' + (err.errMsg || ''),
							icon: 'none'
						});
					}
				});
			},
			statechange(e) {
				console.log("statechange:" + JSON.stringify(e));
			},
			netstatus(e) {
				console.log("netstatus:" + JSON.stringify(e));
			},
			error(e) {
				console.error("error:" + JSON.stringify(e));
				uni.showModal({
					title: '相机错误',
					content: JSON.stringify(e.detail),
					showCancel: false
				});
			},
			/* 兼容 nvue 的二维码解析方法:APP端使用plus.barcode */
			async decodeQR(imageInfo) {
				console.log('decodeQR 图片地址', imageInfo);
				return new Promise(async (resolve) => {
					// #ifdef APP-PLUS
					// 设置识别类型:QR码(二维码)、EAN13/EAN8(商品条码)、CODE128(快递单号常用)
					let filters = [plus.barcode.QR, plus.barcode.EAN13, plus.barcode.EAN8, plus.barcode.CODE128];
					plus.barcode.scan(imageInfo.tempImagePath, function(type, result) {
						console.log("Scan success:(" + type + ") " + result);
						resolve(result);
					}, function(e) {
						console.log("Scan failed: " + JSON.stringify(e));
						resolve(null);
					}, filters);
					// #endif

					
				});
			},
			/* 将本地文件路径转为 base64 */
			pathToBase64(path) {
				return new Promise((resolve, reject) => {
					// #ifdef APP-PLUS
					plus.io.resolveLocalFileSystemURL(path, (entry) => {
						entry.file((file) => {
							const reader = new plus.io.FileReader();
							reader.onloadend = (e) => resolve(e.target.result);
							reader.readAsDataURL(file);
						}, reject);
					}, reject);
					// #endif
					// #ifndef APP-PLUS
					// 非 App 平台可借助 uni.getFileSystemManager 读取
					const fs = uni.getFileSystemManager();
					fs.readFile({
						filePath: path,
						encoding: 'base64',
						success: (res) => {
							resolve('data:image/png;base64,' + res.data);
						},
						fail: reject
					});
					// #endif
				});
			},
			/* 删除文件 */
			deleteFile(filePath) {
				// #ifdef APP-PLUS
				plus.io.resolveLocalFileSystemURL(filePath, function(entry) {
					entry.remove(function(entry) {
						console.log("文件删除成功");
					}, function(e) {
						console.log("文件删除失败: " + e.message);
					});
				}, function(e) {
					console.log("文件路径解析失败: " + e.message);
				});
				// #endif

				// #ifndef APP-PLUS
				uni.getFileSystemManager().unlink({
					filePath: filePath,
					success: () => {
						console.log('文件删除成功');
					},
					fail: (err) => {
						console.log('文件删除失败', err);
					}
				});
				// #endif
			}
		}
	}
</script>

<style>
	.content {
		flex: 1;
		flex-direction: column;
		align-items: center;
		padding: 20px;
		background-color: #f8f8f8;
	}

	.camera-container {
		width: 300px;
		height: 400px;
		background-color: #000;
		border-radius: 10px;
		overflow: hidden;
		position: relative;
	}

	.live-pusher {
		width: 300px;
		height: 400px;
	}

	.permission-hint {
		width: 300px;
		height: 400px;
		align-items: center;
		justify-content: center;
		background-color: #333;
	}

	.permission-text {
		color: #ffffff;
		font-size: 16px;
	}

	.controls {
		flex-direction: row;
		justify-content: space-around;
		width: 700rpx;
		margin-top: 20px;
	}

	.btn {
		padding: 5px 10px;
		background-color: #007aff;
		border-radius: 5px;
	}

	.btn-text {
		color: #ffffff;
		font-size: 14px;
	}

	.preview-box {
		margin-top: 30px;
		flex-direction: column;
		align-items: center;
		width: 700rpx;
	}

	.preview-text {
		font-size: 16px;
		margin-bottom: 10px;
	}

	.preview-img {
		width: 300px;
		height: 400px;
		border-width: 1px;
		border-style: solid;
		border-color: #cccccc;
		border-radius: 10px;
	}
</style>
Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐