【声网】实现web端与uniapp微信小程序端音视频互动

news2024/11/23 23:52:23

实现web端与uniapp微信小程序端音视频互动

利用声网实现音视频互动

开通声网服务

  1. 注册声网账号

  2. 进入Console

  3. 成功登录控制台后,按照以下步骤创建一个声网项目:

    1. 展开控制台左上角下拉框,点击创建项目按钮。

      在这里插入图片描述

    2. 在弹出的对话框内,依次选择项目类型,输入项目名称,选择场景标签鉴权机制。其中鉴权机制推荐选择安全模式(APPID + Token)调试模式的安全性较低。
      在这里插入图片描述

    3. 创建好项目之后可以获取APP ID,Token,和Channel。临时Token有效期为24小时
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

web端

技术栈:vite+vue3+vue-router+pinia

组件库:element plus

开发环境准备:

  • Windows 或 macOS 计算机,需满足以下要求:
    • 下载声网 Web SDK 支持的浏览器。声网强烈推荐使用最新稳定版 Google Chrome 浏览器。
    • 具备物理音视频采集设备。
    • 可连接到互联网。如果你的网络环境部署了防火墙,请参考应用企业防火墙限制以正常使用声网服务。
    • 搭载 2.2 GHz Intel 第二代 i3/i5/i7 处理器或同等性能的其他处理器。
  • 安装 Node.js 及 npm。
  • 有效的声网账户和声网项目,并且从声网控制台获取以下信息:
    • App ID:声网随机生成的字符串,用于识别你的 App。
    • 临时 Token:你的 App 客户端加入频道时会使用 Token 对用户进行鉴权。临时 Token 的有效期为 24 小时。
    • 频道名称:用于标识频道的字符串。

项目开发

  1. 项目初始化

    使用vite创建vue3项目,npm create vite@latest

    集成webSDK,npm install agora-rtc-sdk-ng@latest

    安装vue-router,npm install vue-router@4在src下创建router.js;

    //router.js
    import { createRouter, createWebHistory } from 'vue-router';
    
    const routes = [
      {
        path: '/',
        name: 'Home',
        component: () => import("@/views/home/home.vue")
      },
      {
        path: '/video',
        name: 'Video',
        component: () => import("@/views/video/video.vue")
      },
      
    ];
    
    const router = createRouter({
      history: createWebHistory(),
      routes
    });
    
    export default router;
    

    安装Element Plus ,npm install element-plus --save,并且设置组件自动导入,具体可以看官方文档

    安装Pinia,npm install pinia

    在main.js中初始化pinia,并在主目录创建store目录,创建options.js文件

    //mian.js
    import { createSSRApp } from 'vue'
    import * as Pinia from 'pinia';
    export function createApp() {
      const app = createSSRApp(App)
      app.use(Pinia.createPinia());
      return {
        app,
        Pinia
      }
    }
    
    //options.js
    import { defineStore } from "pinia";
    import {reactive} from "vue";
    
    export const useOptionsStore = defineStore('options',() => {
      const options = reactive({
        appId: "Your APPID",
        token: "Your token",
      })
    
      return {options}
    })
    

    在main.js中引入所有安装的内容:

    import { createApp } from 'vue';
    import router from "./router";
    import { createPinia } from 'pinia';
    import ElementPlus from 'element-plus'
    import 'element-plus/dist/index.css'
    import App from './App.vue';
    
    const app = createApp(App)
    app.use(router)
    app.use(ElementPlus)
    
    app.use(createPinia())
    app.mount('#app')
    
  2. 编写home.vue

​ 使用el-form el-form-item el-input 对用户的输入进行校验,校验通过后携带用户输入的参数进入到视频通话页面

<template>
  <div class="content">
    <el-form
      :model="form"
      label-width="auto"
      style="max-width: 600px"
      :rules="rules"
      ref="ruleFormRef"
    >
      <el-form-item label="房间号" prop="channel">
        <el-input v-model="form.channel" placeholder="请输入房间号"></el-input>
      </el-form-item>
      <el-form-item label="用户名" prop="uid">
        <el-input v-model="form.uid" placeholder="请输入用户名"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="joinChannel" style="width: 100%"
          >加入房间</el-button
        >
      </el-form-item>
    </el-form>
  </div>
</template>
<script setup>
import { reactive, ref } from "vue";
import { useRouter } from "vue-router";

const router = useRouter();

const form = reactive({
  channel: "",
  uid: "",
});

const ruleFormRef = ref(null);

const rules = reactive({
  channel: [{ required: true, message: "请输入房间号", trigger: "blur" }],
  uid: [{ required: true, message: "请输入用户名", trigger: "blur" }],
});

const joinChannel = async () => {
  await ruleFormRef.value.validate();
  router.push({
    name: "Video",
    query: { channel: form.channel, uid: form.uid },
  });
};
</script>
<style scoped>
.content {
  display: flex;
  flex-direction: column;
}
</style>
  1. 编写video.vue

    • 实现视频通话的步骤大致为:初始化客户端对象并创建本地客户端 -> 订阅远端流 -> 加入频道 -> 创建并发布本地音视频轨道-> (通话) ->退出关闭本地流
    1. 初始化客户端对象并创建本地客户端

      导入声网组件,import AgoraRTC from "agora-rtc-sdk-ng";

      初始化客户端对象,let client = AgoraRTC.createClient({ mode: "rtc", codec: "h264" }); 注意:与微信小程序通信codec为h264。与其他端使用的是vp8。并且此client不能写成响应式数据,要保证全局使用的本地客户端对象为唯一的。

      要保证客户端对象可以在最一开始就可以进行初始化,并且可以全局使用,所以都写入setup中。

import AgoraRTC from "agora-rtc-sdk-ng";
let client = AgoraRTC.createClient({ mode: "rtc", codec: "h264" });
  1. 订阅远端流

    当远端流发布到频道时,会触发 user-published 事件,需要通过 client.on 监听该事件并在回调中订阅新加入的远端流。当远端用户取消发布流或退出频道时,触发user-unpublished事件,关闭及移除对应的流。为了用户能够在进入页面的时候及时监听到远端流,所以将监听事件放入onMounted中。

    handleUserPublished函数中编写远端用户加入事件,远端用户加入事件中需要做的事情是通过调用client.subscribe(远端加入的用户user对象,连接方式)方法获取到远端用户对象和连接方式(分为video,audio),区别不同的连接方式,调用包含在user中不同的方法。如果是video,就先需要在先创建dom,将user的视频放入到dom中,并且该dom要有宽高。如果是audio则不用。

    handleUserUnpublished函数中编写远端用户退出事件,就将remoteUserRef的值置为null,使其不再显示页面上。

<template>
   <!-- 使用ref获取dom -->
    远程用户:
    <div
      class="remote-user"
      ref="remoteUserRef"
    ></div>
</template>
      import AgoraRTC from "agora-rtc-sdk-ng";
      import {ref} from "vue";
      let client = AgoraRTC.createClient({ mode: "rtc", codec: "h264" });
      
      
      const remoteUserRef = ref(null)
      const handleUserPublished = async (user, mediaType) => {
        await client.subscribe(user, mediaType);
      
        // 如果是视频轨道,则添加到远程用户列表
        if (mediaType === "video") {
          user.videoTrack.play(remoteUserRef.value);
        }
        // 如果是音频轨道,直接播放
        if (mediaType === "audio") {
          user.audioTrack.play();
        }
      };
      
      const handleUserUnpublished = () => {
        // 移除远程用户
        remoteUserRef.value = null;
      };
      
      onMounted(async () => {
        // 监听远程用户发布事件
        client.on("user-published", handleUserPublished);
        client.on("user-unpublished", handleUserUnpublished);
      });
 .remote-user {
   width: 640px;
   height: 480px;
 }
  1. 加入频道

    调用 client.join(appId, channel, token, uid) 方法加入一个 RTC 频道,需要在该方法中传入 app ID 、用户 ID、Token、频道名称。

    appId,token都可以通过pinia取出,channel和uid通过路由参数取出。client.join是一个异步方法,返回一个promise,使用async,await。

    import { useOptionsStore } from "../store/options";
    import {  useRoute } from "vue-router";
    const route = useRoute();
    const store = useOptionsStore();
    
    const {options} = store;
    
    const { uid , channel} = route.query
    
    const joinChannel = async () => {
      await client.join(
        options.appId,
        channel,
        options.token,
        uid
      );
    };
    
    onMounted(async () => {
      // 监听远程用户发布事件
      client.on("user-published", handleUserPublished);
      client.on("user-unpublished", handleUserUnpublished);
      await joinChannel();
    });
    
  2. 创建并发布本地音视频轨道

调用 AgoraRTC.createMicrophoneAudioTrack() 通过麦克风采集的音频创建本地音频轨道对象,调用 AgoraRTC.createCameraVideoTrack()通过摄像头采集的视频创建本地视频轨道对象;然后调用 client.publish 方法,将这些本地音视频轨道对象当作参数即可将音视频发布到频道中。并且创建一个容器用于播放本地视频轨道。

<template>
    <div
      class="local-player"
      ref="localPlayerRef"
    ></div>
...
</template>
 import { ref , reactive} from "vue";
 ...
 const localUser = reactive({
   videoTrack: null,
   audioTrack: null,
 });
 const localPlayerRef = ref(null);
 const createTrack = async () => {
   localUser.audioTrack = await AgoraRTC.createMicrophoneAudioTrack();
   localUser.videoTrack = await AgoraRTC.createCameraVideoTrack();
   await client.publish([localUser.audioTrack, localUser.videoTrack]);
   localUser.videoTrack.play(localPlayerRef.value);
 }
 
 onMounted(async () => {
   // 监听远程用户发布事件
   client.on("user-published", handleUserPublished);
   client.on("user-unpublished", handleUserUnpublished);
   await joinChannel();
   await createTrack();
 });
 .local-player {
   width: 640px;
   height: 480px;
 }
 </style>
  1. 离开频道
const leaveChannel = async () => {
  localUser.audioTrack && localUser.audioTrack.close();
  localUser.videoTrack && localUser.videoTrack.close();

  // 离开频道
  if (client) {
    await client.leave();
    client = null;
  }

  // 返回首页
  localPlayerRef.value = null;
  remoteUserRef.value = null;
  router.push({ name: "Home" });
};

onBeforeUnmount(() => {
  leaveChannel();
});

整体代码:

 <template>
       <!-- 创建本地视频容器 start-->
       <div>
         本地用户: 
         <div
           class="local-player"
           ref="localPlayerRef"
         ></div>
       </div>
       <!-- 创建本地视频容器 end -->
 
       <!-- 使用 v-for 循环遍历所有远程用户并为每个用户创建一个视频容器 -->
       <div>
         远程用户:
         <div
           class="remote-user"
           ref="remoteUserRef"
         ></div>
       </div>
     </div>
 
     <button @click="leaveChannel" style="margin-top: 20px; display: block">
       退出
     </button>
 </template>
 import {
   ref,
   onMounted,
   onBeforeUnmount,
 } from "vue";
 import AgoraRTC from "agora-rtc-sdk-ng";
 import { useOptionsStore } from "@/store/options";
 import { useRouter, useRoute } from "vue-router";
 
 const router = useRouter();
 
 const route = useRoute();
 
 const localUser = reactive({
   videoTrack: null,
   audioTrack: null,
 });
 
 let client = AgoraRTC.createClient({ mode: "rtc", codec: "h264" });
 
 const localPlayerRef = ref(null);
 const remoteUserRef = ref(null);
 
 const store = useOptionsStore();
 
 const { options } = store;
 
 onBeforeUnmount(() => {
   leaveChannel();
 });
 
 const joinChannel = async () => {
   await client.join(
     options.appId,
     channel,
     options.token,
     uid
   );
 
 };
     
 const createTrack = () => {
       // 创建并发布本地音频和视频轨道
   localUser.audioTrack = await AgoraRTC.createMicrophoneAudioTrack();
   localUser.videoTrack = await AgoraRTC.createCameraVideoTrack();
   await client.publish([localUser.audioTrack, localUser.videoTrack]);
   localUser.videoTrack.play(localPlayerRef.value);
 }
 
 const leaveChannel = async () => {
   localUser.audioTrack && localUser.audioTrack.close();
   localUser.videoTrack && localUser.videoTrack.close();
 
   // 离开频道
   if (client) {
     await client.leave();
     client = null;
   }
 
   // 返回首页
   localPlayerRef.value = null;
   remoteUserRef.value = null;
   router.push({ name: "Home" });
 };
 
 
 const handleUserPublished = async (user, mediaType) => {
   await client.subscribe(user, mediaType);
   if (
     user.uid !== localUser.uid &&
     !remoteUsers.value.some((remoteUser) => remoteUser.uid === user.uid)
   ) {
     remoteUsers.value.push(user);
   }
 
   // 如果是视频轨道,则添加到远程用户列表
   if (mediaType === "video") {
     nextTick(() => {
       const remoteUserRef = remoteUserRefMap.value[`remote_Ref_${user.uid}`];
       if (remoteUserRef) {
         user.videoTrack.play(remoteUserRef);
       }
     });
   }
   // 如果是音频轨道,直接播放
   if (mediaType === "audio") {
     user.audioTrack.play();
   }
 };
 
 const handleUserUnpublished = (user) => {
   // 移除远程用户
   remoteUsers.value = remoteUsers.value.filter(
     (remoteUser) => remoteUser.uid !== user.uid
   );
 };
 
 onMounted(async () => {
   // 监听远程用户发布事件
   client.on("user-published", handleUserPublished);
   client.on("user-unpublished", handleUserUnpublished);
   await joinChannel();
   await createTrack();
 });
 .local-player,
 .remote-user {
   width: 640px;
   height: 480px;
 }

uniapp小程序端

技术栈:uniapp+vue3+pinia

组件库:uni ui

开发环境准备:

  • 下载并安装最新版的微信开发者工具。
  • 一个经过企业认证的微信小程序账号。在调试小程序 Demo 过程中,需要使用 live-pusher 和 live-player 组件。只有特定行业的认证企业账号才可以使用这两个组件。具体组件见微信官方文档
  • 微信小程序账号需要在微信公众平台进行配置。
  • 参考开通服务在控制台创建项目、获取 App ID 和临时 Token,并开启小程序服务。
  • 一台安装有微信 App 的移动设备。

Console开启小程序服务

第一次使用微信小程序时,需要参考如下步骤开通服务:

  1. 登录声网控制台,点击左侧导航栏的全部产品,选择实时互动 RTC,进入实时互动 RTC 页面。
  2. 在实时互动 RTC 页面,切换到功能配置页签。

在这里插入图片描述

微信小程序配置

  1. 获取小程序组件权限

​ 在微信公众平台的小程序开发选项中,切换到接口设置页签,打开实时播放音视频流实时录制音视频流的开关。

在这里插入图片描述

  1. 配置服务器域名

在小程序的开发设置里,将如下域名配到服务器域名里。

request 合法域名区域填入以 https 开头的域名;

   https://uap-ap-web-1.agora.io
   https://uap-ap-web-2.agoraio.cn
   https://uap-ap-web-3.agora.io
   https://uap-ap-web-4.agoraio.cn
   https://report-ad.agoralab.co
   https://rest-argus-ad.agoralab.co
   https://uni-webcollector.agora.io

socket 合法域名区域点入以 wss 开头的域名。

wss://miniapp.agoraio.cn

在这里插入图片描述

项目开发

  1. 初始化项目

    HBuilder X 可视化(推荐)或者cli脚手架创建uniapp项目。 具体创建方式

    创建项目后,需要集成小程序SDK。有两种方式:

    • 第一种:下载小程序SDK 并解压。

      将解压出来的JS文件复制到项目中主目录的static下,在主目录下创建一个utils,创建一个sdk.js,引入并导出该SDK,用于vue文件可以引入并使用。

      const AgoraMiniappSDK = require('../static/Agora_Miniapp_SDK_for_WeChat.js');
      
      export default AgoraMiniappSDK
      
    • 第二种方式:npm 安装

      npm install agora-miniapp-sdk
      
    1. 使用pinia存入音视频所需要的APP ID 和临时token

      如果使用的是HBuilder X 可视化创建的项目,可以直接使用pinia,使用cli脚手架创建的需要手动通过npm install pinia安装

      • 在main.js中初始化pinia,并在主目录创建store目录,创建options.js文件

        //mian.js
        import { createSSRApp } from 'vue'
        import * as Pinia from 'pinia';
        export function createApp() {
          const app = createSSRApp(App)
          app.use(Pinia.createPinia());
          return {
            app,
            Pinia
          }
        }
        
        //options.js
        import { defineStore } from "pinia";
        import {reactive} from "vue";
        
        export const useOptionsStore = defineStore('options',() => {
          const options = reactive({
            appId: "Your APPID",
            token: "Your token",
          })
        
          return {options}
        })
        
    2. 引入uni ui组件库

      如果使用HBuilder X ,可以直接通过uni ui组件库导入到项目中

    3. 编写路由文件Pages.json

      暂定只有两个页面,index页面用于输入房间号和用户id,video文件用于视频通话

      {
      	"pages": [
      		{
      			"path": "pages/index/index",
      			"style": {
      				"navigationBarTitleText": "主页"
      			}
      		},
      		{
      			"path": "pages/video/video",
      			"style": {
      				"navigationStyle":"custom"
      			}
      		}
      	],
      	"globalStyle": {
      		"navigationBarTextStyle": "black",
      		"navigationBarTitleText": "uni-app",
      		"navigationBarBackgroundColor": "#F8F8F8",
      		"backgroundColor": "#F8F8F8"
      	},
      	"uniIdRouter": {}
      }
      
    4. 编写index页面

      ​ 使用uni-forms``````uni-forms-item``````uni-easyinput对用户的输入进行校验,校验通过后携带用户输入的参数进入到视频通话页面

      <template>
        <view class="contain">
          <uni-forms :modelValue="formData" ref="form" :rules="rules">
            <uni-forms-item name="channle">
              <uni-easyinput
                type="text"
                v-model="formData.channle"
                placeholder="请输入房间号"
              />
            </uni-forms-item>
            <uni-forms-item name="uid">
              <uni-easyinput
                type="text"
                v-model="formData.uid"
                placeholder="请输入用户uid"
              />
            </uni-forms-item>
            <button @click="joinChannle">进入房间</button>
          </uni-forms>
        </view>
      </template>
      
      <script setup>
      import { ref } from "vue";
      
      const formData = ref({
        channle: "",
        uid: "",
      });
      
      const form = ref(null);
      
      const rules = {
        channle: {
          rules: [{ required: true, errorMessage: "请输入房间号", trigger: "blur" }],
        },
        uid: {
          rules: [{ required: true, errorMessage: "请输入用户uid", trigger: "blur" }],
        },
      };
      
      const joinChannle = async () => {
        await form.value.validate();
        uni.navigateTo({
          url: `/pages/video/video? 
          channle=${formData.value.channle}&uid=${formData.value.uid}`,
        });
      };
      </script>
      
      <style scoped lang="scss">
      ::v-deep(.contain) {
        display: flex;
        flex-direction: column;
        align-items: center;
        height: 100vh;
      
        .uni-forms {
          width: 80%;
          margin-top: 40%;
          .uni-easyinput {
            .uni-easyinput__content {
              height: 90rpx;
            }
          }
      
          button {
            width: 100%;
            height: 80rpx;
            line-height: 80rpx;
            background-color: #409eff;
            color: #fff;
            border: none;
            border-radius: 10rpx;
            margin-top: 40rpx;
          }
        }
      }
      </style>
      
    5. 编写video.vue

      • 实现视频通话的步骤大致为:初始化客户端对象 -> 订阅远端流 -> 加入频道 -> 发布本地流 -> (通话) ->退出关闭本地流

      • 可以用以下的图片概括

        在这里插入图片描述

        一、代码实现步骤

        1. 初始化客户端对象

          ​ 引入SDK对象 import AgoraMiniappSDK from "../../utils/sdk"

          ​ 在setup中实例化客户端对象let client = new AgoraMiniappSDK.Client();在setup中创建是因为可以最一开始就创建好对象,并且在全局可以访问到同一个客户端对象。实例化好客户端对象后,需要进行初始化,调用的是client.init(appId)方法,appId通过pinia取出,此方法是一个异步函数,返回一个promise对象,可以使用.then(),也可以使用async、await方法。但在setup语法糖中不能直接使用async修饰,所以需要将方法包裹在函数中。

          ​ 在这里会使用微信小程序的onLoad生命周期函数,正好也可以接收从index页面传过来的路由参数。在vue3中,onLoad函数需要引入。import { onLoad } from "@dcloudio/uni-app";

          ​ 到这里,代码就如同:

          <template>
          </template>
          
          <script setup>
          import AgoraMiniappSDK from "../../utils/sdk";
          import { useOptionsStore } from "../../store/options";
          import { onLoad } from "@dcloudio/uni-app;
              
          const store = useOptionsStore();
          const { appId, token } = store.options;
              
          let client = new AgoraMiniappSDK.Client();
          let channel,uid;
          
          onLoad(async (options) => {
            if (options) {
              channel = options.channle;
              uid = options.uid;
            }
            await client.init(appId);
          });
              
          </script>
          
        2. 订阅远端流

          ​ 当远端流发布到频道时,会触发 stream-added 事件,需要通过 client.on 监听该事件并在回调中订阅新加入的远端流。当远端用户取消发布流或退出频道时,触发stream-remove事件,关闭及移除对应的流。为了用户能够在进入页面的时候及时监听到远端流,所以将监听事件放入onLoad中。

          onLoad(async (options) => {
            if (options) {
              channel = options.channle;
              uid = options.uid;
            }
            client.on("stream-added", handleAdd);
            client.on("stream-removed", handleRemove);
            await client.init(appId);
          });
          

          ​ 在handleAdd函数中编写远端用户加入事件,远端用户加入事件中需要做的事情是通过调用client.subscribe(远端加入的用户uid)方法获取到远端用户的远端音视频地址,并将地址绑定在微信的<live-player>的src上,就可以在页面上显示远端用户的视频。所以需要在<template><live-player>组件,也需要定义一个变量livePlayer存入地址。

          ​ 在handleRemove函数中编写远端用户退出事件,就将livePlayer的值置为null,使其不再显示页面上。

          <template>
            <view>
              <!-- 远端拉流 start -->
              <live-player
                class="live-view"
                id="player"
                :src="livePlayer"
                mode="RTC"
                :autoplay="true"
              />
          	<!-- mode为模式,RTC为实时通话,autoplay一定要设置了true才会自动播放 -->      
              <!-- 远端拉流 end -->
            </view>
          </template>
          
          <script setup>
          import {ref} from "vue"
          import AgoraMiniappSDK from "../../utils/sdk";
          import { useOptionsStore } from "../../store/options";
          import { onLoad } from "@dcloudio/uni-app;
              
          const store = useOptionsStore();
          const { appId, token } = store.options;
              
          let client = new AgoraMiniappSDK.Client();
          let channel,uid;
          
          const livePlayer = ref(null);
          
          const handleAdd = async (e) => {
            const { url } = await client.subscribe(e.uid);//远端用户uid
            livePlayer.value = url;
          };
          
          const handleRemove = async (e) => {
            livePlayer.value = null;
          };
              
          onLoad(async (options) => {
            if (options) {
              channel = options.channle;
              uid = options.uid;
            }
            await client.init(appId);
          });
          </script>
          
        3. 加入频道

          ​ 调用 client.join(yourToken, channel, uid) 方法加入频道。此方法是异步函数

          ​ 在 client.join 中你需要将 yourToken 替换成你自己生成的 Token。并填入想要加入的频道名以及用户 uid。为了能够及时加入频道,并且使得整个流程清晰,所以将join方法写在onLoad中。

          <template>
            <view>
              ...
            </view>
          </template>
          
          <script setup>
          ...
              
          const joinChannle = async () => {
            await client.join(token, channel, uid);
          };
          
          ...
              
          onLoad(async (options) => {
            if (options) {
              channel = options.channle;
              uid = options.uid;
            }
            await client.init(appId);
            await joinChannle();
          });
          </script>
          
        4. 发布本地流

          ​ 成功加入频道后,就能调用 client.publish 方法将本地音视频流发布到频道中。成功发布后,SDK 会返回该路音视频流的 URL。URL用于微信小程序的组件<live-pusher>。同理,需要设置变量pusherPlayer存入地址。需要调用client.setRole(“broadcaster”)方法先将推流的角色设置为主播(broadcaster),才能推流,并且<live-pusher>的autopush需要设置为true

        const livePlayer = ref(null);
        const joinChannle = async () => {
          await client.setRole("broadcaster");
          await client.join(token, channel, uid);
          pusherPlayer.value = await client.publish();
        };
        
        1. 退出关闭本地流

          调用client.cleave()方法退出频道。

        整体代码

      <template>
        <view>
            <!-- 本地推流 start -->
              <live-pusher
                :url="pusherPlayer"
                mode="RTC"
                :autopush="true"
                class="pusher"
              />
            <!-- 本地推流 end -->
      
          <!-- 远端拉流 start -->
          <live-player
            class="live-view"
            :src="livePlayer"
            mode="RTC"
            style="width: 100vw; height: 100vh"
            :autoplay="true"
          />
          <!-- 远端拉流 end -->
      
          <!-- 功能按钮 start -->
          <view> <i class="iconfont hang-up return-icon" @click="goBack"></i></view>
          <!-- 功能按钮 end -->
        </view>
      </template>
      
      <script setup>
      import { ref,  computed, watch } from "vue";
      import AgoraMiniappSDK from "../../utils/sdk";
      import { useOptionsStore } from "../../store/options";
      import { onLoad } from "@dcloudio/uni-app";
      
      const store = useOptionsStore();
      
      const { appId, token } = store.options;
      
      let channel, uid;
      
      let client = new AgoraMiniappSDK.Client();
      
      const pusherPlayer = ref(null);
      const livePlayer = ref(null);
      
      
      const joinChannle = async () => {
        await client.init(appId);
        await client.setRole("broadcaster");
        await client.join(token, channel, uid);
        pusherPlayer.value = await client.publish();
      };
      
      const handleAdd = async (e) => {
        const { url } = await client.subscribe(e.uid);
        livePlayer.value = url;
      };
      
      const handleRemove = async (e) => {
        livePlayer.value = null;
      };
      
      const goBack = () => {
        uni.showModal({
          title: "提示",
          content: "确定要退出直播吗?",
          success: (res) => {
            if (res.confirm) {
              client.leave();
              uni.navigateBack();
            }
          },
        });
      };
      
      onLoad(async (options) => {
        if (options) {
          channel = options.channle;
          uid = options.uid;
        }
        client.on("stream-added", handleAdd);
        client.on("stream-removed", handleRemove);
        await joinChannle();
      });
      
      </script>
      
      
      
      
      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1627137.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机视觉——OpenCV 使用分水岭算法进行图像分割

分水岭算法 分水岭算法&#xff1a;模拟地理形态的图像分割 分水岭算法通过模拟自然地形来实现图像中物体的分类。在这一过程中&#xff0c;每个像素的灰度值被视作其高度&#xff0c;灰度值较高的像素形成山脊&#xff0c;即分水岭&#xff0c;而二值化阈值则相当于水平面&am…

Pytorch 的神经网络 学习笔记

参照官方网址 Module — PyTorch 2.2 documentation 一. 介绍 1. torch.nn模块&#xff1a; torch.nn是PyTorch中专门用于构建神经网络的模块。它提供了构建深度学习模型所需的所有构建块&#xff0c;包括各种层类型&#xff08;如全连接层、卷积层、循环层等&#xff09;、…

Web3解密:理解去中心化应用的核心原理

引言 在当前数字化时代&#xff0c;去中心化技术和应用正在逐渐引起人们的关注和兴趣。Web3技术作为去中心化应用&#xff08;DApps&#xff09;的基础&#xff0c;为我们提供了一个全新的互联网体验。但是&#xff0c;对于许多人来说&#xff0c;这个复杂的概念仍然充满了神秘…

【源码】WBF多语言交易所/申购+自发币平台币+币币+杠杆+合约/附带安装教程/带VUE工程源码

【源码介绍】 WBF多语言交易所/申购自发币平台币币币杠杆合约/附带安装教程/带VUE工程源码 【源码说明】 带VUE工程源码最新申购&#xff0c;自发币平台币&#xff0c;币币&#xff0c;法币&#xff0c;杠杆&#xff0c;合约多语言交易所&#xff0c;附带pc和手机VUE&#x…

Grafana系列 | Grafana监控TDengine库数据 |Grafana自定义Dashboard

开始前可以去grafana官网看看dashboard文档 https://grafana.com/docs/grafana/latest/dashboards 本文主要是监控TDengine库数据 目录 一、TDengine介绍二、Grafana监控TDengine数据三、Grafana自定义Dashboard 监控TDengine库数据1、grafana 变量2、添加变量3、配置panel 一…

CasinoRoyale靶机练习实践报告

CasinoRoyale靶机练习实践报告 下载地址: https://drive.google.com/open?id1FYP246L63zShV00wOckAQ5F5XJ4HkZ0Lhttps://download.vulnhub.com/casinoroyale/CasinoRoyale.ovahttps://download.vulnhub.com/casinoroyale/CasinoRoyale.ova.torrent ( Magnet) 1 安装靶机 …

乘数而上,创邻科技入选2024数商典型应用场景“乘数榜”

4月18日&#xff0c;由浙江省科学技术协会指导的2024未来数商大会在杭州成功举办。本次大会以“场景突破 乘数而上”为主题&#xff0c;国际国内数商共聚未来科技城学术交流中心&#xff0c;聚焦数据要素市场的制度创新、数据治理、场景应用与生态构建等话题展开研讨。 大会现…

【算法刷题 | 贪心算法04】4.26(跳跃游戏、跳跃游戏||)

文章目录 6.跳跃游戏6.1题目6.2解法&#xff1a;贪心6.2.1贪心思路6.2.2代码实现 7.跳跃游戏||7.1题目7.2解法&#xff1a;贪心7.2.1贪心思路7.2.2代码实现 6.跳跃游戏 6.1题目 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你…

数据分析:宏基因组分析-从Raw data到profile的简单流程

简介 该宏基因组流程主要有四步&#xff0c;分别是1.检查raw data&#xff1b;2.获得高质量reads&#xff1b;3.合并PE数据&#xff1b;4. reads map到参考数据库得到profile。 初步想法&#xff1a; 先分别撰写每一步的基础脚本&#xff0c;如过滤&#xff0c;mapping等过程…

HarmonyOS实战开发-如何在鸿蒙开发中使用数据库

鸿蒙中的数据库基于SQLite组件&#xff0c;用来处理关系比较复杂的数据&#xff0c;本文将以WORKER表为例&#xff0c;为大家演示在鸿蒙开发中对数据库的增删改查操作。 1、首先导入数据库模块&#xff1a; import relationalStore from ohos.data.relationalStore;2、配置数…

Linux平台Unity下RTMP|RTSP低延迟播放器技术实现

技术背景 国产操作系统对于确保信息安全、促进技术创新、满足特定需求以及推动经济发展等方面都具有重要意义&#xff0c;多以Linux为基础二次开发。2014年4月8日起&#xff0c;美国微软公司停止了对Windows XP SP3操作系统提供支持&#xff0c;这引起了社会和广大用户的广泛关…

从零入门区块链和比特币(第二期)

欢迎来到我的区块链与比特币入门指南&#xff01;如果你对区块链和比特币感兴趣&#xff0c;但不知道从何开始&#xff0c;那么你来对地方了。本博客将为你提供一个简明扼要的介绍&#xff0c;帮助你了解这个领域的基础知识&#xff0c;并引导你进一步探索这个激动人心的领域。…

四:物联网ARM开发

一&#xff1a;ARM体系结构概述 1&#xff1a;控制外设led灯还有一些按键这些就要用到gpio&#xff0c;采集传感器的数据需要adc进行转化数据格式&#xff0c;特殊的外设和传感器是通过特殊的协议接口去进行连接的比如一些轴传感器和主控器的连接是通过spi&#xff0c;IIC 控制…

Python+Django网站指纹信息侦测探查

程序示例精选 PythonDjango网站指纹信息侦测探查 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonDjango网站指纹信息侦测探查》编写代码&#xff0c;代码整洁&#xff0c;规则&#…

Docker与Linux容器:“探索容器化技术的奥秘”

目录 一、Docker概述 二、容器技术的起源&#xff1a; 三、Linux容器 四、Docker的出现 五、Docker容器特点&#xff1a; 六、Docker三大概念&#xff1a; 容器&#xff1a; 镜像&#xff1a; 仓库&#xff1a; 七、Docker容器常用命令 一、Docker概述 在云原生时代&…

玩机进阶教程------高通刷机 纯adb脚本操作刷写分区 备份分区的一些简单操作步骤解析

目前来说大多数刷机平台都是使用官方提供的工具。但一般这类工具刷写校验较多。例如小米刷机平台miflash和高通qpst平台。都对于电脑系统刷写环境有一定的要求。而且平台刷写校验md5等等。虽然可以通过修改脚本去除类似校验。但还是有必要了解一些纯adb脚本来刷写9008固件的方法…

智能变频三模正弦波控制器

智能变频三模正弦波控制器 前言一、图片介绍总结 前言 不敢动&#xff0c;完全不敢动。多做笔记&#xff0c;完全了解之后再说吧 一、图片介绍 轮毂电机 主角登场 淘宝关于这款控制器的介绍 当然不同的型号功能不同 学习线插上就会转,可以使用继电器控制通断。 电门…

[论文阅读] 3D感知相关论文简单摘要

Adaptive Fusion of Single-View and Multi-View Depth for Autonomous Driving 提出了一个单、多视图融合深度估计系统&#xff0c;它自适应地集成了高置信度的单视图和多视图结果 动态选择两个分支之间的高置信度区域执行融合 提出了一个双分支网络&#xff0c;即一个以单…

本地生活服务平台哪家强,怎么申请成为服务商?

当下&#xff0c;本地生活服务已经成为了多家互联网大厂布局的重要板块&#xff0c;在巨大的市场需求和强大的资本加持下&#xff0c;不少人都看到了本地生活服务平台广阔的前景和收益空间。在此背景下&#xff0c;许多普通人都跃跃欲试&#xff0c;想要成为本地生活服务商&…

Spark AQE 导致的 Driver OOM问题

背景 最近在做Spark 3.1 升级 Spark 3.5的过程中&#xff0c;遇到了一批SQL在运行的过程中 Driver OOM的情况&#xff0c;排查到是AQE开启导致的问题&#xff0c;再次分析记录一下&#xff0c;顺便了解一下Spark中指标的事件处理情况 结论 SQLAppStatusListener 类在内存中存…