前提 需要做出这样的一个效果 (以下为代码效果):
过程:
在uniapp官网内 处理内嵌视频流可以发现livePusher这样一个组件,通过这个组件 可以实现 在页面弹窗或者某个部位实时播放摄像头的画面。
uniapp 官网文档抛出了以下事件,来控制处理组件。
同时 需要在模块配置中勾选livePusher模块;
以上为官网内的例子,但是在页面使用的时候 发现了 摄像头内嵌画面出现了,但是发现事件的回调不执行,拿不到参数。
官网问答社区出现了相同问题 作者提出用nvue方案 但是我使用一样没有效果。
官网文档中有这样一段画 使用plus.video.LivePusher 同时查看uniapp livepusher 源码 看到了该方法;
通过 https://www.html5plus.org/doc/zh_cn/video.html#plus.video.createLivePusher 找到该方法 ,博主用的是livePusher对象处理重新写了一个简陋的直播推流组件;
<template>
<view>
<web-view style="background-color: #fff;" :webview-styles="{height:0,width:0}" id='webViewsss' src="">
<!-- <cover-image v-if="data.isSlot" class="photograp_content_coverView" src="/static/images/home/livePusherBg.png"></cover-image> -->
<slot v-if="data.isSlot"></slot>
</web-view>
</view>
</template>
livepusher对象 需要内嵌在webView内 因此 需要创建一个webView
const pages = getCurrentPages();
const page = pages[pages.length - 1];
data.currentWebview = page.$getAppWebview(); //页面栈最顶层就是当前webview
通过此代码 找到页面栈最顶层就是当前webview
// 获取dom距离窗口的位置 控制webView 位置
const getElmentTopLeft = async (className) => {
return new Promise(reslove => {
const query = uni.createSelectorQuery().in(pageData._this)
query.select(className).boundingClientRect(data => {
if (data) {
reslove(data)
}
}).exec()
})
}
该方法为传入的为需要放置的盒子的class 通过传入类名 获取距离当前窗口的位置,从而控制webView的位置;
data.pusher = await plus.video.createLivePusher("livePusher1", {
url: 'rtmp://192.168.99.4:1935/live/client01',
left: 15,
top: 10,
right: 15,
bottom: 10,
width: objDom.width - 30,
height: objDom.height - 20,
position: 'absolute',
mode: 'FHD',
muted: true
});
通过plus Api创建一个livePusher对象;
await data.currentWebview.children().forEach((v, index) => {
if (index > 0) {
plus.webview.close(v);
}
})
data.currentWebview.children()[0].append(data.pusher)
在将livePusher添加到webView前需要将多余的webView关闭 避免livePusher添加到了看不见的webView;
const ws = data.currentWebview.children()[0];
ws.setStyle({
height: objDom.height,
left: objDom.left,
top: objDom.top,
width: objDom.width,
})
通过传入的位置 控制当前webView的方位;
// 快照
const snapshotPusher = () => {
return new Promise( (resolve, reject) => {
data.pusher.snapshot(async function(e) {
const url = await getMinImage(e.tempImagePath)
resolve(url)
}, function(e) {
console.log(e);
reject(e)
})
})
}
const getMinImage = (imgPath) => {
return new Promise((resolve, reject) => {
plus.zip.compressImage({
src: imgPath,
dst: imgPath,
overwrite: true,
quality: 40
},
zipRes => {
console.log(zipRes);
resolve(zipRes.target)
},
function(error) {
console.log('出错了');
}
);
})
}
通过plus api 执行快照事件 拿到回调数据 ,同时通过压缩图片 并抛出图片进制地址 ;
// 抛出销毁事件
const destoryLisvPusher = () => {
data.pusher.stop()
const pages = getCurrentPages();
const page = pages[pages.length - 1];
const currentWebview = page.$getAppWebview();
currentWebview.children().forEach((v, index) => {
plus.webview.close(v);
})
data.isSlot = false
}
写一个 抛出销毁事件 因为livePusher 在app 抓拍多次 会出现app闪退问题 因为结合业务 每次抓拍后 则关闭销毁当前组件
onUnmounted(() => {
const pages = getCurrentPages();
const page = pages[pages.length - 1];
const currentWebview = page.$getAppWebview();
currentWebview.children().forEach((v, index) => {
plus.webview.close(v);
})
})
在生命周期内 再次销毁 做一个保险;
<livePusher ref='facialRecofnLiveRusher'>
<cover-image class="photograp_content_coverView "
src="/static/images/home/livePusherBg.png"></cover-image>
</livePusher>
通过slot 将cover-image覆盖在livePusher 上 ;
通过slot问题 在组件销毁的时候 控制台会出现报错 不过不影响运行;uniapp官网上也会有这个问题;想要不报错,将slot内容直接写在webView内 即可;
uniapp:uni-app官网
html5+:HTML5+ API Reference
总结:通过livePusher创建对象 与webView 结合封装了一个livePusher组件;其实可以直接用html5+ 的直播推流控件对象来创建,更简单,不过我也懒得去尝试了;webView还可以动态生成,有兴趣的可以自己去尝试一下;以下为所有代码:
组件代码:
<template>
<view>
<web-view style="background-color: #fff;" :webview-styles="{height:0,width:0}" id='webViewsss' src="">
<!-- <cover-image v-if="data.isSlot" class="photograp_content_coverView" src="/static/images/home/livePusherBg.png"></cover-image> -->
<slot v-if="data.isSlot"></slot>
</web-view>
</view>
</template>
<script setup>
import blobVue from './blob.vue'; //可以忽略 没有用
import {
onMounted,
reactive,
ref,
getCurrentInstance,
onUnmounted
} from 'vue';
import {
onReady
} from '@dcloudio/uni-app'
const data = reactive({
_this: null,
snapshotsrc: null, //快照
livePusher: null,
currentWebview: null,
isSlot: true
})
const pops = defineProps({
isWidthAll: {
type: Boolean,
default: false
}
})
onUnmounted(() => {
const pages = getCurrentPages();
const page = pages[pages.length - 1];
const currentWebview = page.$getAppWebview();
currentWebview.children().forEach((v, index) => {
plus.webview.close(v);
})
})
// 生成livePusher
const pusherInit = async (objDom) => {
const pages = getCurrentPages();
const page = pages[pages.length - 1];
// #ifdef APP-PLUS
data.currentWebview = page.$getAppWebview(); //页面栈最顶层就是当前webview
setTimeout(async function() {
// const objDom = await getElmentTopLeft('.photograp_content')
// 此处为业务需要
if (pops.isWidthAll) {
data.pusher = await plus.video.createLivePusher("livePusher1", {
url: 'rtmp://192.168.99.4:1935/live/client01',
left: 15,
top: 10,
right: 15,
bottom: 10,
width: objDom.width - 30,
height: objDom.height - 20,
position: 'absolute',
mode: 'FHD',
muted: true
});
} else {
data.pusher = await plus.video.createLivePusher("livePusher1", {
url: 'rtmp://192.168.99.4:1935/live/client01',
left: 10,
top: 10,
right: 10,
bottom: 10,
width: objDom.width - 20,
height: objDom.height - 20,
position: 'absolute',
mode: 'FHD',
muted: true
});
}
// 反转相机
// data.pusher.switchCamera()
await data.currentWebview.children().forEach((v, index) => {
if (index > 0) {
plus.webview.close(v);
}
})
data.currentWebview.children()[0].append(data.pusher)
const ws = data.currentWebview.children()[0];
ws.setStyle({
height: objDom.height,
left: objDom.left,
top: objDom.top,
width: objDom.width,
})
// 监听错误事件
data.pusher.addEventListener('statechange', function(e) {
console.log('statechange: ' + JSON.stringify(e));
// console.log('statechange: '+e);
}, false);
}, 500)
// #endif
}
// 获取dom位置
const getElmentTopLeft = async (className) => {
return new Promise(reslove => {
const query = uni.createSelectorQuery().in(data._this)
query.select(className).boundingClientRect(data => {
if (data) {
reslove(data)
}
}).exec()
})
}
// 快照
const snapshotPusher = () => {
return new Promise( (resolve, reject) => {
data.pusher.snapshot(async function(e) {
const url = await getMinImage(e.tempImagePath)
resolve(url)
}, function(e) {
console.log(e);
reject(e)
})
})
}
const getMinImage = (imgPath) => {
return new Promise((resolve, reject) => {
plus.zip.compressImage({
src: imgPath,
dst: imgPath,
overwrite: true,
quality: 40
},
zipRes => {
console.log(zipRes);
resolve(zipRes.target)
},
function(error) {
console.log('出错了');
}
);
})
}
// 抛出销毁事件
const destoryLisvPusher = () => {
data.pusher.stop()
const pages = getCurrentPages();
const page = pages[pages.length - 1];
const currentWebview = page.$getAppWebview();
currentWebview.children().forEach((v, index) => {
plus.webview.close(v);
})
data.isSlot = false
}
defineExpose({
pusherInit,
snapshotPusher,
destoryLisvPusher
})
</script>
<style>
</style>
事件调用:
<template>
<view class="photograp_content">
<livePusher ref='settleLiveRusher'>
<cover-image class="photograp_content_coverView "
src="/static/images/home/livePusherBg.png"></cover-image>
</livePusher>
</view>
</template>
<script setup>
import { ref } from 'vue'
const settleLiveRusher = ref('')
// 生成
setTimeout(async () => {
const objDom = await getElmentTopLeft('.photograp_content')
nextTick(() => {
settleLiveRusher.value.pusherInit(objDom)
})
}, 500)
// 销毁
const facialRecoModelClose = () => {
facialRecofnLiveRusher.value.destoryLisvPusher()
}
// 获取dom距离窗口的位置 控制webView 位置
const getElmentTopLeft = async (className) => {
return new Promise(reslove => {
const query = uni.createSelectorQuery().in(pageData._this)
query.select(className).boundingClientRect(data => {
if (data) {
reslove(data)
}
}).exec()
})
}
</script>