@zjp179280/web-call-components

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.
856
Install
npm install @zjp179280/web-call-components
Latest Version:0.1.1
Vue:3
License:MIT
Last Updated:Oct 9, 2026
Links: npm
Maintainer: zjp179280

@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 反馈:

  1. MQTT 地址校验:缺少 mqtt.url 或不是 ws/wss 开头 → 直接报错,不发起呼叫。
  2. MQTT 连接校验:连接成功 / 重连中 / 断开 / 失败都会实时回传状态徽标。
  3. HTTP 配置校验:若未配置 http.baseURL,控制台给出警告(不阻断,因为通话走 MQTT)。
  4. 媒体设备校验:摄像头/麦克风权限被拒绝、设备不存在、被占用时,回传中文错误提示。
  5. targetId 校验:缺少对端设备 ID 时提示。
  6. 30 秒无应答:自动转为 no_answer 并取消呼叫。
  7. 资源释放:关闭/卸载时停止轨道、关闭 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:

  • 公共:panel header title-plate title title-line close mqtt avatar peer-name peer-code status-text btn(以及具体按钮 btn-mute btn-speaker btn-hangup btn-cancel btn-confirm btn-camera)。
  • 语音:另有 body peer top-controls stage wave duration。
  • 视频:另有 stage banner(重连横幅)remote-video overlay pulse(脉冲环)local-pip local-video control-bar call-info。

提示:part="btn btn-mute" 这样的元素同时拥有两个 part 名——::part(btn) 命中所有按钮,::part(btn-mute) 只命中麦克风钮。

9.3 头像

三种方式(优先级从高到低):

  1. 属性传图片地址(推荐):<wc-audio-call avatar-url="https://xxx/a.png">,或 JS el.avatarUrl = url。
  2. CSS 变量铺图:wc-audio-call { --wc-avatar-image: url(https://xxx/a.png); }。
  3. 都不配时,显示 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、对端设备号,点击「发起语音/视频通话」即可联调。

Related Packages

@tencentcloud/roomkit-web-vue3

腾讯云 TUIRoomKit 音视频房间 UI 组件库(Vue3 版本)。提供会前预检与会中两套完整...

189,303 161
@tencentcloud/roomkit-electron-vue3

TUIRoomKit Conference (TUIRoomKit) is a product suitable for multi-person audio...

64,154 161
@applooma/rtc-vue

AppLooma RTC Vue SDK — composables and components for real-time voice, video and...

495
live-avatar-element

Framework-agnostic Web Component for Azure Voice Live interactive avatar assista...

2,393

Version History

Version Released Vue License
0.1.1 ^3.5.0 MIT
0.1.0 ^3.5.0 MIT
0.0.3 ^3.5.0 MIT
0.0.2 ^3.5.0 MIT
0.0.1 ^3.5.0 MIT