uniapp采用直播推流组件live-pusher 实现扫码,保存图片。
·
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>
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)