一、需要完成1v1视频聊天效果
1、效果展示
2、实现代码
主要是使用live-player、live-pusher
2.1 live-player
实时音视频播放,也称直播拉流,通俗的解释一下就是就是接受聊天对面视频的容器,你和朋友在微信小程序里视频聊天,你的手机需要接收对方的视频,然后播放出来,这就是 live-player
的作用。如果你是app不是使用 live-player,而是直接使用 video 组件
2.2 属性说明(live-player | uni-app官网)查文档就行
2.3 live-pusher
实时音视频录制,也称直播推流。通俗点来说,live-pusher
就是一个把你的摄像头和麦克风采集到的视频和音频,推送到服务器的工具。这块也就是把你摄像头对着的内容(自拍的话就是你自己)推到服务器上。
2.4 属性live-pusher | uni-app官网
url就是推流地址,还有什么美颜啥的看文档即可
2.4 live-pusher demo (这个可直接推自己的流)
<template><view><live-pusher id='livePusher' ref="livePusher" class="livePusher" url=""mode="SD" :muted="true" :enable-camera="true" :auto-focus="true" :beauty="1" whiteness="2"aspect="9:16" @statechange="statechange" @netstatus="netstatus" @error = "error"></live-pusher><button class="btn" @click="start">开始推流</button><button class="btn" @click="pause">暂停推流</button><button class="btn" @click="resume">resume</button><button class="btn" @click="stop">停止推流</button><button class="btn" @click="snapshot">快照</button><button class="btn" @click="startPreview">开启摄像头预览</button><button class="btn" @click="stopPreview">关闭摄像头预览</button><button class="btn" @click="switchCamera">切换摄像头</button></view>
</template>
<script>export default {data() {return {}},onReady() {// 注意:需要在onReady中 或 onLoad 延时this.context = uni.createLivePusherContext("livePusher", this);},methods: {statechange(e) {console.log("statechange:" + JSON.stringify(e));},netstatus(e) {console.log("netstatus:" + JSON.stringify(e));},error(e) {console.log("error:" + JSON.stringify(e));},start: function() {this.context.start({success: (a) => {console.log("livePusher.start:" + JSON.stringify(a));}});},close: function() {this.context.close({success: (a) => {console.log("livePusher.close:" + JSON.stringify(a));}});},snapshot: function() {this.context.snapshot({success: (e) => {console.log(JSON.stringify(e));}});},resume: function() {this.context.resume({success: (a) => {console.log("livePusher.resume:" + JSON.stringify(a));}});},pause: function() {this.context.pause({success: (a) => {console.log("livePusher.pause:" + JSON.stringify(a));}});},stop: function() {this.context.stop({success: (a) => {console.log(JSON.stringify(a));}});},switchCamera: function() {this.context.switchCamera({success: (a) => {console.log("livePusher.switchCamera:" + JSON.stringify(a));}});},startPreview: function() {this.context.startPreview({success: (a) => {console.log("livePusher.startPreview:" + JSON.stringify(a));}});},stopPreview: function() {this.context.stopPreview({success: (a) => {console.log("livePusher.stopPreview:" + JSON.stringify(a));}});}}}
</script>
2.5 liveplayer demo (这个得放一个拉流地址)
<live-playersrc="https://domain/pull_stream"autoplay@statechange="statechange"@error="error"style="width: 300px; height: 225px;"
/>
export default {methods:{statechange(e){console.log('live-player code:', e.detail.code)},error(e){console.error('live-player error:', e.detail.errMsg)}}
}
3、点击切换大屏以及全屏拖动
3.1 切换大小屏逻辑
<live-playerv-if="isSwapped"class="big-box":src="pullUrl"autoplay@click="toggleStreamSize"
/>
<live-pusherv-elseclass="big-box":url="pusherUrl"mode="SD"@click="toggleStreamSize"
/>
.big-box {width: 100%;height: 100%;position: absolute;z-index: 1;
}
这个是大屏 big-box
<live-pusherv-if="isSwapped"class="small-box":url="pusherUrl"mode="SD"@click="toggleStreamSize"
/>
<live-playerv-elseclass="small-box":src="pullUrl"autoplay@click="toggleStreamSize"
/>
.small-box {width: 150px;height: 205px;/* border: 2px solid #fff; */border-radius: 10px;cursor: pointer;
}
这个是小屏
methods: {toggleStreamSize() {this.isSwapped = !this.isSwapped;}
}
isSwapped === true
live-player
变成大窗live-pusher
变成小窗isSwapped === false
live-pusher
变成大窗live-player
变成小窗
3.2 小窗拖动全屏效果movable-area
movable-area
:定义可拖动的区域,内部包含 movable-view
。
movable-view
:可在 movable-area
内部自由拖动的组件。
属性 | 作用 |
direction | 允许拖拽的方向,可选 all (全方向)、horizontal (水平)、vertical (垂直) |
x / y | 设定初始坐标 |
damping | 移动回弹效果,数值越大越柔和 |
friction | 阻尼系数,数值越小,惯性滚动距离越长 |
out-of-bounds | 允许拖出 movable-area (默认 false ) |
animation | 拖拽回弹是否有动画(默认 true ) |
4、完整代码
<template><view class="container"><!-- 大窗组件 --><live-playerv-if="isSwapped"class="big-box":src="pullUrl"autoplay@statechange="handleStateChange"@error="handleError"@click="toggleStreamSize"/><live-pusherv-elseclass="big-box":url="pusherUrl"mode="SD":muted="true":enable-camera="true":auto-focus="true":beauty="1"whiteness="2"aspect="9:16"@statechange="handleStateChange"@netstatus="handleNetStatus"@error="handleError"@click="toggleStreamSize"/><!-- 小窗包装层,用于处理拖动 --><movable-area class="small-wrapper"><movable-viewclass="small-box"direction="all":style="dragStyle"x="500" y="0"drag><!-- 小窗组件 --><live-pusherv-if="isSwapped"class="small-box":url="pusherUrl"mode="SD":muted="true":enable-camera="true":auto-focus="true":beauty="1"whiteness="2"aspect="9:16"@statechange="handleStateChange"@netstatus="handleNetStatus"@error="handleError"@click="toggleStreamSize"/><live-playerv-elseclass="small-box":src="pullUrl"autoplay@statechange="handleStateChange"@error="handleError"@click="toggleStreamSize"/></movable-view></movable-area><view class="bottomimg"><image class="liveshimg" src="@/static/livepush/camera.png" @click="stopPreview" /><image class="liveshimg" src="@/static/livepush/over.png" @click="stop" /><image class="liveshimg" src="@/static/livepush/reversal.png" @click="switchCamera" /></view></view>
</template><script>export default {data() {return {// 当 isSwapped 为 true 时,live-pusher 显示为小窗;反之 live-player 为小窗isSwapped: false,pusherUrl: "",pullUrl: "https://domain/pull_stream",bookoldid: "",callid: "",// 拖动相关数据(针对小窗包装层)offsetX: 10,offsetY: 10,startX: 0,startY: 0,dragging: false,// 固定的小窗尺寸(需与样式保持一致)boxWidth: 150,boxHeight: 200,};},computed: {...mapState(['openid']),// 使用 transform: translate 设置小窗包装层位置dragStyle() {return `transform: translate(${this.offsetX}px, ${this.offsetY}px); position: absolute;`;},},onReady() {this.context = uni.createLivePusherContext("livePusher", this);},methods: {// 点击切换大窗和小窗toggleStreamSize() {this.isSwapped = !this.isSwapped;},handleStateChange(e) {console.log("statechange:" + JSON.stringify(e));},handleNetStatus(e) {console.log("netstatus:" + JSON.stringify(e));},handleError(e) {console.log("error:" + JSON.stringify(e));},start() {this.context.start({success: (a) => {console.log("livePusher.start:" + JSON.stringify(a));},});},close() {this.context.close({success: (a) => {console.log("livePusher.close:" + JSON.stringify(a));},});},snapshot() {this.context.snapshot({success: (e) => {console.log(JSON.stringify(e));},});},resume() {this.context.resume({success: (a) => {console.log("livePusher.resume:" + JSON.stringify(a));},});},pause() {this.context.pause({success: (a) => {console.log("livePusher.pause:" + JSON.stringify(a));},});},stop() {this.context.stop({success: (a) => {console.log(JSON.stringify(a));uni.switchTab({ url: "/pages/ShoppingCart/ShoppingCart" });},});},switchCamera() {this.context.switchCamera({success: (a) => {console.log("livePusher.switchCamera:" + JSON.stringify(a));},});},startPreview() {this.context.startPreview({success: (a) => {console.log("livePusher.startPreview:" + JSON.stringify(a));},});},stopPreview() {this.context.stopPreview({success: (a) => {console.log("livePusher.stopPreview:" + JSON.stringify(a));},});},},
};
</script><style>
.container {width: 100%;height: 100vh;position: relative;overflow: hidden;
}
.big-box {width: 100%;height: 100%;position: absolute;z-index: 1;
}
.small-box {width: 150px;height: 205px;/* border: 2px solid #fff; */border-radius: 10px;cursor: pointer;
}
.liveshimg {width: 100rpx;height: 100rpx;
}.small-wrapper {z-index: 2;width: 100vw;height: 100vh;}.bottomimg {display: flex;justify-content: space-around;position: absolute;bottom: 10%;left: 0;width: 100%;z-index: 3;
}</style>
复制完整代码即可