uniapp+微信小程序+仿微信视频聊天+点击小窗切换+全屏拖动小窗(live-player+live-pusher)+完整代码

一、需要完成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 (这个可直接推自己的流)
javascript 复制代码
<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 (这个得放一个拉流地址)
javascript 复制代码
<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 切换大小屏逻辑
javascript 复制代码
<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

javascript 复制代码
<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;
}

这个是小屏

javascript 复制代码
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、完整代码
javascript 复制代码
<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>

复制完整代码即可

相关推荐
Planck_Ho8 小时前
uni-app 小程序表单校验错误自动滚动到错误位置
微信小程序·uni-app
xiyueta10 小时前
使用cursor ai 开发 UniApp JSON 工具开发文档
uni-app·json
浮生若梦l15 小时前
原生android 打包.aar到uniapp使用
android·java·uni-app
优雅格子衫15 小时前
记录uniapp小程序对接腾讯IM即时通讯无ui集成(2)
小程序·uni-app
尚学教辅学习资料16 小时前
基于SSM+Vue+uniapp的考研交流(带商城)小程序+LW示例参考
vue.js·考研·uni-app
ZJL-阿友16 小时前
uniapp简单可分享的电子名片
uni-app
啊吨吨吨OOO。17 小时前
uniapp+vue3搭建项目
前端·javascript·uni-app
优雅格子衫17 小时前
uniapp小程序对接腾讯IM即时通讯无ui集成(1)
小程序·uni-app
小钟H呀17 小时前
uniapp 常用 UI 组件库
ui·uni-app