@zjp179280/web-call-components
v0.1.1
Published
Native Web Components for 1:1 audio/video calls, multi-window VOD/monitoring (with voice intercom) and multi-party video meeting (rooms, layouts, host controls, screen share, local recording). MQTT signaling + WebRTC. Works with Vue2/Vue3/React/vanilla.
Maintainers
Readme
@zjp179280/web-call-components
基于 MQTT 信令 + WebRTC 的一对一语音 / 视频通话组件,封装为原生 Web Component,Vue2 / Vue3 / 原生 HTML 均可直接使用。
- 语音通话:
<wc-audio-call> - 视频通话:
<wc-video-call>
信令逻辑完整移植自原项目 mqtStore.js 的一对一通话路径(type 0/1/5/6/7/8/9/10/11/1012/1014/1015),去除了 Pinia / Element Plus / 全局 store 依赖,所有外部配置通过 config 属性注入。
一、安装
npm install @zjp179280/web-call-components本地开发时可通过
file:../web-call-components直接链接源码包(见同级web-call-components-demo)。
二、最快上手(Vue3)
<template>
<button @click="start">发起语音通话</button>
<wc-audio-call
v-if="show"
ref="callEl"
peer-name="设备A"
@call-status-change="onCall"
@mqtt-status-change="onMqtt"
@error="onError"
@close="show = false"
></wc-audio-call>
</template>
<script setup>
import { ref, nextTick } from 'vue'
import '@zjp179280/web-call-components' // 样式已内联进各元素 Shadow DOM,无需再引入 css
const show = ref(false)
const callEl = ref(null)
async function start() {
show.value = true
await nextTick()
// 对象配置必须以 DOM property 方式注入(不能写在模板属性里)
callEl.value.config = {
mqtt: {
url: 'ws://your-mqtt-broker:8083/mqtt',
clientId: 'Web~10001',
username: '10001',
password: '你的token'
},
rtc: { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] },
user: { id: '10001', name: '管理员' },
targetId: '设备号'
}
}
// 注意:自定义元素 DOM 事件的载荷统一放在 event.detail[0](Vue 把 emit 参数放进 detail 数组)
function onCall(e) { console.log('通话状态', e.detail[0]) }
function onMqtt(e) { console.log('MQTT', e.detail[0]) }
function onError(e) { console.error(e.detail[0]) }
</script>视频通话同理,把标签换成 <wc-video-call> 即可。
Vue3 + Vite 工程必做:在
vite.config.js告诉编译器wc-开头的标签是原生自定义元素(否则对象 props / 事件会被当成 Vue 组件处理而异常):// vite.config.js import vue from '@vitejs/plugin-vue' export default { plugins: [ vue({ template: { compilerOptions: { isCustomElement: (t) => t.startsWith('wc-') } } }) ] }Vue3 +
vue.config(CLI / webpack)则用compilerOptions.isCustomElement链式配置。
二·五、推荐:先连接 MQTT,确认成功后再拨号(两步法)
组件支持注入一个已连接好的 CallEngine,把“连 MQTT”和“拨号”拆开,便于先校验连接:
import { CallEngine } from '@zjp179280/web-call-components'
let engine = null
// 第一步:连接 MQTT
async function connectMqtt(config) {
engine = new CallEngine(config)
engine.on('mqttStatus', (s) => console.log('MQTT', s.status, s.message))
await engine.connect() // 会等待真正连上并订阅 p2p 主题;失败会抛错
}
// 第二步:拨号(复用上面的连接,组件不会重复连 MQTT)
async function startVoice(config) {
const el = document.createElement('wc-audio-call')
el.engine = engine // 注入已连接引擎(关键)
el.config = config // 仍需传 config,用于 targetId / user 等
document.body.appendChild(el)
}- 传入
engine后,组件不再自建 MQTT 连接,通话结束也不会断开这条连接,可连续拨打。 - 不传
engine时,组件会在挂载后用config自行连接(一步法,向后兼容)。 - 完整可运行示例见同级工程
web-call-components-demo(页面上就是「① 连接 MQTT → ② 发起通话」两个按钮)。
三、在 Vue2 中使用
Vue2 需要告诉编译器这些标签是原生自定义元素:
// main.js
import Vue from 'vue'
import App from './App.vue'
import '@zjp179280/web-call-components' // 样式已内联进各元素 Shadow DOM,无需再引入 css
Vue.config.ignoredElements = [/^wc-/]
new Vue({ render: h => h(App) }).$mount('#app')<template>
<wc-audio-call
v-if="show"
ref="call"
peer-name="设备A"
@call-status-change.native="onCall"
@mqtt-status-change.native="onMqtt"
@close.native="show = false"
></wc-audio-call>
</template>
<script>
export default {
data: () => ({ show: false }),
methods: {
start() {
this.show = true
this.$nextTick(() => {
this.$refs.call.config = { /* 同上方 config */ }
})
}
}
}
</script>Vue2 监听原生自定义事件需要加
.native。
四、在原生 HTML 中使用
<script type="module">
import '@zjp179280/web-call-components' // 样式已内联,无需引 css
const call = document.createElement('wc-audio-call')
call.peerName = '设备A'
call.config = {
mqtt: { url: 'ws://your-mqtt-broker:8083/mqtt', clientId: 'Web~1', username: '1', password: 'token' },
rtc: { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] },
user: { id: '1', name: '管理员' },
targetId: '设备号'
}
call.addEventListener('call-status-change', (e) => console.log(e.detail[0]))
call.addEventListener('close', () => call.remove())
document.body.appendChild(call)
</script>五、完整 config 配置项
| 字段 | 类型 | 必填 | 说明 |
| ---------------------- | ------------- | -- | ----------------------------- |
| mqtt.url | string | ✅ | MQTT 地址,必须 ws:// 或 wss:// |
| mqtt.clientId | string | ✅ | 本端客户端 ID,与后端约定(如 Web~用户ID) |
| mqtt.username | string | 否 | 连接用户名(通常传用户 ID) |
| mqtt.password | string | 否 | 连接密码(登录 token) |
| mqtt.extraTopics | string[] | 否 | 额外订阅的业务主题 |
| mqtt.reconnectPeriod | number | 否 | 重连间隔,默认 5000ms |
| rtc.iceServers | array | 否 | STUN/TURN 服务器,默认 Google STUN |
| rtc.bitrate | number | 否 | 视频发送码率上限,默认 1000000(1Mbps) |
| user.id | string | 否 | 本端用户 ID,用于 extra_data |
| user.name | string | 否 | 本端用户名,用于 extra_data |
| targetId | string/number | ✅ | 对端设备 ID(也可用标签 target-id 属性) |
| topics.call | string | 否 | 呼叫发布主题,默认 device/call |
| topics.p2pPrefix | string | 否 | 响应主题前缀,默认 device/p2p/ |
| http.baseURL | string | 否 | 业务接口地址(可选;通话本身走 MQTT) |
| http.headers | object | 否 | 业务接口默认请求头 |
| log | boolean | 否 | 是否打印调试日志,默认 true |
六、事件
组件派发以下 DOM 事件(CustomEvent)。Vue 自定义元素约定:emit 的参数被放进 event.detail 数组,业务载荷取 event.detail[0](close 无载荷):
| 事件 | event.detail[0] | 说明 |
| -------------------- | --------------------------- | ----------------------------------------------------------------------- |
| call-status-change | { message, errorMessage } | 通话状态:init/dialing/connected/rejected/ended/no_answer/errorMessageText |
| mqtt-status-change | { status, message } | MQTT 状态:connecting/connected/reconnecting/disconnected/error |
| error | Error | 致命错误(取 event.detail[0].message) |
| close | — | 用户关闭面板 |
el.addEventListener('call-status-change', (e) => {
const s = e.detail[0] // ← 载荷在数组首项
console.log(s.message, s.errorMessage)
})直接使用
CallEngine实例(不走 DOM 事件)时,engine.on('callStatus', s => ...)回调里的s就是载荷本身,无需[0]。
七、暴露的方法
通过 ref 调用:
callEl.value.hangup() // 通话中挂断
callEl.value.cancel() // 拨打中取消
callEl.value.close() // 关闭面板八、内置校验(重要)
组件在发起前会依次做以下校验,并通过 mqtt-status-change / call-status-change / error 反馈:
- MQTT 地址校验:缺少
mqtt.url或不是ws/wss开头 → 直接报错,不发起呼叫。 - MQTT 连接校验:连接成功 / 重连中 / 断开 / 失败都会实时回传状态徽标。
- HTTP 配置校验:若未配置
http.baseURL,控制台给出警告(不阻断,因为通话走 MQTT)。 - 媒体设备校验:摄像头/麦克风权限被拒绝、设备不存在、被占用时,回传中文错误提示。
- targetId 校验:缺少对端设备 ID 时提示。
- 30 秒无应答:自动转为
no_answer并取消呼叫。 - 资源释放:关闭/卸载时停止轨道、关闭 PeerConnection、断开音频元素。
九、样式自定义(CSS 变量 + ::part + 头像)
组件运行在 Shadow DOM 内:宿主页面的普通 CSS 选择器进不去(因此不会被全局样式污染,也不会泄漏样式),但有两条标准通道可以深度定制。所有样式已随 JS 内联进各自定义元素,无需、也不要再引入任何 css 文件。
9.1 方式一:覆盖 CSS 变量(推荐)
CSS 自定义属性会穿透 Shadow 边界。在你的页面里对元素本身(或 :root)赋值即可,随改随生效:
/* 单个组件定制 */
wc-audio-call {
--wc-panel-bg: #0b1f33; /* 面板底色(默认深蓝 rgb(0,29,55)) */
--wc-primary: #13c2c2; /* 主色:确定按钮、拨打波点、头像渐变起点 */
--wc-accent: #ffd000; /* 底部 L 形切角颜色 */
--wc-corner-size: 22px; /* 切角大小,设 0 可隐藏切角 */
--wc-avatar-size: 84px; /* 头像大小 */
}
/* 全局统一改两个组件 */
:root {
--wc-hangup: #e53935;
--wc-panel-radius: 12px;
}可用变量(均在内部 :host 上给了默认值):
| 分类 | 变量 | 默认值 | 作用 |
| ---- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------- |
| 品牌色 | --wc-primary | #1890ff | 主色(确定按钮、波点、头像渐变起点) |
| | --wc-primary-strong | #0275cf | 主色深色 |
| | --wc-cyan | #00ffff | 通话中“激活/选中”描边 |
| | --wc-accent | #18d9f5 | 切角、高光 |
| | --wc-border | rgba(117,190,255,.9) | 面板边框 |
| | --wc-hangup / --wc-hangup-hover / --wc-hangup-icon | #f44336 / #ff6659 / #fe5e5e | 挂断按钮 |
| | --wc-danger / --wc-success / --wc-warning | #f56c6c / #67c23a / #ff9800 | 错误 / 正常 / 重连状态色 |
| 面板 | --wc-panel-bg | #001d37 | 面板背景 |
| | --wc-panel-radius | 8px | 面板圆角 |
| | --wc-panel-border-width | 2px | 边框宽度 |
| | --wc-panel-glow / --wc-panel-shadow | 内发光 + 外投影 | 面板光效(可设 none 关闭) |
| | --wc-corner / --wc-corner-size | var(--wc-accent) / 16px | 底部 L 形切角颜色 / 大小(0 隐藏) |
| 文字 | --wc-text / --wc-text-dim | #fff / rgba(255,255,255,.7) | 主文字 / 次文字 |
| | --wc-font / --wc-title-font | 系统字体栈 / Fira Code 等宽 | 正文字体 / 标题字体 |
| | --wc-title-size / --wc-name-size / --wc-code-size / --wc-status-size | 17/15/12/15px | 标题 / 名称 / 编号 / 状态字号 |
| 标题底板 | --wc-title-plate-bg / --wc-title-plate-border | 蓝色渐变 / 半透明蓝边 | 标题底板背景 / 描边 |
| | --wc-title-line | 蓝色渐变线 | 标题下分隔线 |
| 头像 | --wc-avatar-size | 72px | 头像直径 |
| | --wc-avatar-bg | linear-gradient(135deg,#1890ff,#36cfc9) | 无图片时的头像底(首字头像) |
| | --wc-avatar-border / --wc-avatar-shadow | none | 头像描边 / 阴影 |
| | --wc-avatar-image | none | 设为 url(...) 可直接铺头像图 |
| 按钮 | --wc-btn-sm / --wc-btn-md / --wc-btn-lg | 36/42/48px | 小 / 中 / 大圆钮尺寸 |
| | --wc-btn-bg / --wc-btn-bg-video / --wc-btn-hover | rgba(0,78,108,.7) / rgba(255,255,255,.2) / rgba(255,255,255,.22) | 语音钮 / 视频控制条钮 / 悬停底 |
| | --wc-btn-icon | 22px | 按钮图标大小 |
| 尺寸 | --wc-audio-width | 300px | 语音面板宽 |
| | --wc-video-width / --wc-video-height | min(960px,94vw) / 540px | 视频舞台宽 / 高 |
| | --wc-video-stage-radius | 8px | 视频舞台圆角 |
| | --wc-pip-width / --wc-pip-height | 200px / 132px | 本地小窗宽 / 高(可拖拽) |
| | --wc-pulse-size | 240px | 视频拨打中脉冲圆环直径 |
9.2 方式二:::part() 精准改结构元素
组件已把关键结构通过 part 暴露,可用 ::part() 直接改某个部位(背景、模糊、边框等):
/* 视频底部控制条加毛玻璃 */
wc-video-call::part(control-bar) { backdrop-filter: blur(8px); background: linear-gradient(to top, rgba(0,0,0,.8), transparent); }
/* 自定义“确定”按钮 */
wc-audio-call::part(btn-confirm) { background: #00b359; border-radius: 8px; }
/* 头像外圈 */
wc-audio-call::part(avatar) { border: 2px solid #fff; box-shadow: 0 0 12px rgba(255,255,255,.6); }暴露的 part:
- 公共:
panelheadertitle-platetitletitle-lineclosemqttavatarpeer-namepeer-codestatus-textbtn(以及具体按钮btn-mutebtn-speakerbtn-hangupbtn-cancelbtn-confirmbtn-camera)。 - 语音:另有
bodypeertop-controlsstagewaveduration。 - 视频:另有
stagebanner(重连横幅)remote-videooverlaypulse(脉冲环)local-piplocal-videocontrol-barcall-info。
提示:
part="btn btn-mute"这样的元素同时拥有两个 part 名——::part(btn)命中所有按钮,::part(btn-mute)只命中麦克风钮。
9.3 头像
三种方式(优先级从高到低):
- 属性传图片地址(推荐):
<wc-audio-call avatar-url="https://xxx/a.png">,或 JSel.avatarUrl = url。 - CSS 变量铺图:
wc-audio-call { --wc-avatar-image: url(https://xxx/a.png); }。 - 都不配时,显示
peer-name的第一个字,底为--wc-avatar-bg渐变。
9.4 视频本地小窗
通话中右上角(默认定位,可拖到舞台任意位置)的本地预览窗,尺寸用 --wc-pip-width / --wc-pip-height 调整,边框 / 镜像可通过 ::part(local-pip)、::part(local-video) 覆盖;关闭摄像头时本地窗会自动变暗。
十、目录结构
web-call-components/
├── src/
│ ├── core/CallEngine.js # MQTT+WebRTC 信令引擎(移植自 mqtStore)
│ ├── components/
│ │ ├── AudioCall.ce.vue # <wc-audio-call>
│ │ └── VideoCall.ce.vue # <wc-video-call>
│ ├── styles/theme.css # 集中管理的设计变量与公共样式
│ └── index.js # 注册自定义元素
└── dist/ # 构建产物十一、发布到 npm
⚠️ 不要把账号密码写进任何文件。请在 npmjs.com 生成 Automation Token。
# 1. 登录(推荐用 token)
npm login
# 2. 构建
npm run build
# 3. 发布(首次发布需要 --access public)
npm publish --access public发布后,业务项目即可 npm install @zjp179280/web-call-components 使用。
十二、本地测试项目111
同级目录 web-call-components-demo 是一个 Vue3 测试工程:
cd web-call-components-demo
npm install
npm run dev # 默认 http://localhost:5199打开页面后填写 MQTT 地址、clientId、对端设备号,点击「发起语音/视频通话」即可联调。
