实现web端与uniapp微信小程序端音视频互动
利用声网实现音视频互动
开通声网服务
-
注册声网账号
-
进入Console
-
成功登录控制台后,按照以下步骤创建一个声网项目:
-
展开控制台左上角下拉框,点击创建项目按钮。
-
在弹出的对话框内,依次选择项目类型,输入项目名称,选择场景标签和鉴权机制。其中鉴权机制推荐选择安全模式(APPID + Token),调试模式的安全性较低。
-
创建好项目之后可以获取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 小时。
- 频道名称:用于标识频道的字符串。
项目开发
-
项目初始化
使用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')
-
编写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>
-
编写video.vue
- 实现视频通话的步骤大致为:初始化客户端对象并创建本地客户端 -> 订阅远端流 -> 加入频道 -> 创建并发布本地音视频轨道-> (通话) ->退出关闭本地流
-
初始化客户端对象并创建本地客户端
导入声网组件,
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" });
-
订阅远端流
当远端流发布到频道时,会触发
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;
}
-
加入频道
调用
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(); });
-
创建并发布本地音视频轨道
调用 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>
- 离开频道
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