一、需要完成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-player
src="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-player
v-if="isSwapped"
class="big-box"
:src="pullUrl"
autoplay
@click="toggleStreamSize"
/>
<live-pusher
v-else
class="big-box"
:url="pusherUrl"
mode="SD"
@click="toggleStreamSize"
/>
.big-box {
width: 100%;
height: 100%;
position: absolute;
z-index: 1;
}
这个是大屏 big-box
<live-pusher
v-if="isSwapped"
class="small-box"
:url="pusherUrl"
mode="SD"
@click="toggleStreamSize"
/>
<live-player
v-else
class="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-player
v-if="isSwapped"
class="big-box"
:src="pullUrl"
autoplay
@statechange="handleStateChange"
@error="handleError"
@click="toggleStreamSize"
/>
<live-pusher
v-else
class="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-view
class="small-box"
direction="all"
:style="dragStyle"
x="500" y="0"
drag
>
<!-- 小窗组件 -->
<live-pusher
v-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-player
v-else
class="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>
复制完整代码即可