bl-trtc-callkit-vue
v1.4.1
Published
基于腾讯云 TRTC SDK 开发的 Vue 3 通话组件,支持一对多音视频通话、网络质量监测、动态视频质量调整等功能。
Readme
bl-trtc-callkit-vue
基于腾讯云 TRTC SDK 开发的 Vue 3 通话组件,支持一对多音视频通话、网络质量监测、动态视频质量调整等功能。
1.4.0版本之后支持两种鉴权方式:① 前端传入
sdkAppId + sdkSecretKey本地计算userSig(开发调试用);② 传入服务端返回的sdkAppId + userSig,更安全(推荐生产环境使用)。1.4.0之前版本仅支持①方式。
更新日志
1.4.1 1.修复挂断后重新接受邀请时远端用户视频不显示的问题; 2.优化无摄像头的远端用户不再创建空视频视图。
1.4.0
1.init 方法新增 userSig 参数,支持传入服务端返回的 userSig 进入房间,避免前端暴露 sdkSecretKey;
2.保留本地 sdkAppId + sdkSecretKey 计算 userSig 的方式,供开发调试使用。
3.新增窗口最小化/最大化功能,支持拖拽调整悬浮窗位置
4.新增前后摄像头切换功能
5.调整底部控制面板布局,添加不可用状态样式
1.3.2
1.远端用户视图支持点击放大,单个远端用户时也支持点击放大;
2.新增 remoteVideoExpandStyle prop,可自定义远端视频放大后的尺寸和宽高比;
1.3.1
1.远端用户视图支持点击放大;
1.3.0
1.新增邀请房间内的其他用户加入通话功能;
2.支持自定义房间号;
3.解决了多人通话时,一个人退出导致所有人都退出的问题;
3.新增统一 action 事件,覆盖进入/离开房间、开关麦克风/摄像头、发起/接听/挂断/拒绝通话、发送/接受/拒绝邀请、远端用户进出等操作回调。将 remote-user-status-change 保留的同时添加至 action 事件(分成了:REMOTE_USER_ENTER、REMOTE_USER_EXIT);
4.添加了 pagehide 和 visibilitychange 的监听,通过在 init 传入 enableVisibilitySuspend 来控制是否开启visibilitychange 监听,visibilitychange 主要是用来解决包括微信自带的浏览器在内的手机浏览器直接从后台杀死进程而无法触发 beforeunload 和 pagehide,导致用户不能离开房间(通话中和来电显示不触发visibilitychange);
5.修复了一些问题;
1.2.0
新增多用户通话支持
安装
npm install bl-trtc-callkit-vue使用示例
基本用法
方式1:前端本地计算 userSig(开发调试)
<script setup>
import { ref, onMounted } from 'vue'
import CallKit from 'bl-trtc-callkit-vue';
const callKitRef = ref(null)
const localUserId = ref(String(Math.floor(Math.random() * 900 + 100)))
const remoteUsers = ref([])
const selectedUsers = ref([])
function onNotify({ type, text }) {
console[type]('🚀 ~ CallKit notify', text)
showToast(text, type)
}
// 统一操作事件处理
function onAction(type, payload) {
switch (type) {
case 'REMOTE_USER_ENTER':
if (!remoteUsers.value.find(u => u.userId === payload.userId)) {
remoteUsers.value.push({ userId: payload.userId })
}
break
case 'REMOTE_USER_EXIT': {
const idx = remoteUsers.value.findIndex(u => u.userId === payload.userId)
if (idx !== -1) {
remoteUsers.value.splice(idx, 1)
const selectedIdx = selectedUsers.value.indexOf(payload.userId)
if (selectedIdx !== -1) selectedUsers.value.splice(selectedIdx, 1)
}
break
}
}
}
// 切换用户选择状态
function toggleUserSelection(userId) {
const index = selectedUsers.value.indexOf(userId)
if (index === -1) {
selectedUsers.value.push(userId)
} else {
selectedUsers.value.splice(index, 1)
}
}
// 呼叫选中的用户
function callSelectedUsers() {
if (selectedUsers.value.length === 0) {
alert('请先选择要呼叫的用户')
return
}
callKitRef.value.handleCall(selectedUsers.value)
}
onMounted(() => {
// 初始化:传入 userId、sdkAppId、sdkSecretKey(本地计算 userSig)
callKitRef.value.init({
userId: localUserId.value,
sdkAppId: "YOUR_SDK_APP_ID",
sdkSecretKey: "YOUR_SDK_SECRET_KEY",
profile: "1080p"
})
})
const toastList = ref([]);
// 生成唯一ID
function generateId() {
return Date.now() + Math.random()
}
// 显示 Toast
function showToast(text, type = 'info') {
const id = generateId()
const toast = {
id,
text,
type
}
toastList.value.push(toast)
// 3秒后自动移除
setTimeout(() => {
const index = toastList.value.findIndex(item => item.id === id)
if (index !== -1) {
toastList.value.splice(index, 1)
}
}, 3000)
}
// 获取图标
function getIcon(type) {
switch (type) {
case 'error':
return '❌'
case 'info':
default:
return 'ℹ️'
}
}
</script>
<template>
<div class="wrapper">
<div style="font-size: 1rem;">当前 userId:<strong>{{ localUserId }}</strong></div>
<div style="margin-top:12px; border:1px solid #ccc; padding:10px; border-radius:4px;">
<h4>远端用户列表</h4>
<ul>
<li v-for="user in remoteUsers" :key="user.userId"
:class="{ selected: selectedUsers.includes(user.userId) }"
@click="toggleUserSelection(user.userId)">
{{ user.userId }}
</li>
</ul>
<button @click="callSelectedUsers" style="height: 36px;padding: 0 8px; margin-top: 12px;">呼叫选中用户</button>
</div>
<CallKit ref="callKitRef" @notify="onNotify" @action="onAction" />
<!-- Toast 通知组件 -->
<div class="toast-container">
<transition-group name="toast">
<div v-for="toast in toastList" :key="toast.id" :class="['toast-item', `toast-${toast.type}`]">
<span class="toast-icon">{{ getIcon(toast.type) }}</span>
<span class="toast-text">{{ toast.text }}</span>
</div>
</transition-group>
</div>
</div>
</template>
<style lang="scss" scoped>
.wrapper {
max-width: 1200px;
margin: 0 auto;
padding: 12px;
display: flex;
flex-direction: column;
gap: 8px;
box-sizing: border-box;
.remote-user-call {
margin-top: 12px;
display: flex;
gap: 8px;
align-items: center
}
ul {
margin-top: 12px;
}
li {
height: 32px;
display: flex;
align-items: center;
background: #eee;
border-radius: 4px;
padding: 6px 8px;
margin-bottom: 12px;
cursor: pointer;
transition: background-color 0.2s;
&.selected {
background: #e3f2fd;
border-left: 4px solid #2196f3;
}
}
}
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 10px;
max-width: 300px;
}
.toast-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
animation: slideInRight 0.3s ease;
min-width: 250px;
max-width: 100%;
}
.toast-info {
background-color: #e3f2fd;
color: #1976d2;
border-left: 4px solid #2196f3;
}
.toast-error {
background-color: #ffebee;
color: #d32f2f;
border-left: 4px solid #f44336;
}
.toast-icon {
margin-right: 10px;
font-size: 18px;
}
.toast-text {
flex: 1;
word-break: break-word;
}
/* 动画效果 */
.toast-enter-active,
.toast-leave-active {
transition: all 0.3s ease;
}
.toast-enter-from {
opacity: 0;
transform: translateX(100%);
}
.toast-leave-to {
opacity: 0;
transform: translateX(100%);
position: absolute;
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
</style>方式2:服务端返回 userSig(生产环境推荐)
<script setup>
import { ref, onMounted } from 'vue'
import CallKit from 'bl-trtc-callkit-vue';
const callKitRef = ref(null)
async function fetchUserSig(userId) {
// 请求自己的服务端接口获取 userSig
const res = await fetch('/api/get-user-sig?userId=' + userId)
return res.json() // 返回 { userSig: '...' }
}
onMounted(async () => {
const userId = String(Math.floor(Math.random() * 900 + 100))
const { userSig } = await fetchUserSig(userId)
// 初始化:传入 userId、sdkAppId、userSig(无需 sdkSecretKey)
callKitRef.value.init({
userId,
sdkAppId: "YOUR_SDK_APP_ID",
userSig, // 服务端返回的 userSig
profile: "1080p"
})
})
</script>
<template>
<CallKit ref="callKitRef" @notify="onNotify" @action="onAction" />
</template>自定义邀请面板
组件提供了 invite-item 插槽以便自定义邀请列表项样式。插槽抛出 itemData(用户对象)、isInvited(是否已邀请)和 invite(发起邀请的方法)。
<template>
<CallKit ref="callKitRef" @notify="onNotify" @action="onAction">
<template #invite-item="{ itemData, isInvited, invite }">
<div class="custom-invite-item">
<span>{{ itemData.name || itemData }}</span>
<button v-if="!isInvited" @click="invite(itemData.id || itemData)">
邀请加入
</button>
<span v-else>已邀请</span>
</div>
</template>
</CallKit>
</template>同时可通过 invitableUsers prop 传入自定义用户列表:
const customUsers = [
{ id: '555', name: '张三' },
{ id: '666', name: '李四' },
]<CallKit :invitable-users="customUsers" />自定义远端视频放大尺寸
通过 remoteVideoExpandStyle prop 可以自定义远端视频点击放大后的尺寸和宽高比。支持三种配置方式:
方式1:设置宽高比(推荐)
<!-- 放大后统一使用 16:18 的宽高比 -->
<CallKit :remote-video-expand-style="{ ratio: '16/18' }" />方式2:精确指定宽高
<!-- 放大后固定为 800px × 900px -->
<CallKit :remote-video-expand-style="{ width: '800px', height: '900px' }" />方式3:完整配置
<CallKit
:remote-video-expand-style="{
ratio: '16/9', // 放大后的宽高比
width: '80vw', // 放大后的宽度(优先级高于 ratio)
height: '90vh', // 放大后的高度(优先级高于 ratio)
maxWidth: 'calc(100vw - 120px)', // 最大宽度
maxHeight: 'calc(100vh - 120px)' // 最大高度
}"
/>| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| ratio | string | — | 放大后的宽高比,如 '16/9'、'3/4'、'16/18' |
| width | string | 'auto' | 放大后的宽度,如 '800px'、'80vw',优先级高于 ratio |
| height | string | 'auto' | 放大后的高度,如 '900px'、'90vh',优先级高于 ratio |
| maxWidth | string | 'calc(100vw - 120px)' | 最大宽度限制 |
| maxHeight | string | 'calc(100vh - 120px)' | 最大高度限制 |
注意:不传
remoteVideoExpandStyle时,放大后的视频保持原始宽高比,仅受最大尺寸限制。
API 说明
Props
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| invitableUsers | Array | null | 自定义可邀请用户列表 |
| invitePanelStyle | Object | null | 自定义邀请面板样式 |
| remoteVideoExpandStyle | Object | null | 自定义远端视频放大后的尺寸和宽高比,见上方「自定义远端视频放大尺寸」 |
组件方法
| 方法名 | 参数 | 说明 |
|-------|------|------|
| init | { userId: string, sdkAppId: number, sdkSecretKey?: string, userSig?: string, profile?: string, roomId?: string, allowInvite?: boolean, enableVisibilitySuspend?: boolean } | 初始化组件。支持两种鉴权方式:① 传入 sdkAppId + sdkSecretKey 本地计算 userSig;② 传入 sdkAppId + userSig(由服务端返回,无需 sdkSecretKey)。其他参数:视频分辨率(默认1440p,支持360p、720p、1080p、1440p)、房间 ID(默认8888)、是否允许邀请(默认 true)、是否启用页面切后台自动挂起/恢复(默认 true) |
| handleCall | targetIds: string[] | 发起呼叫,传入目标用户 ID 数组或者单个用户 ID 字符串 |
| show | 无 | 显示通话组件 |
| hide | 无 | 隐藏通话组件 |
| hangup | 无 | 挂断通话 |
| setVideoProfile | profile: string | 设置视频分辨率 |
| handleEnter | 无 | 进入通话房间 |
| handleExit | 无 | 退出通话房间 |
组件事件
| 事件名 | 回调参数 | 说明 |
|-------|----------|------|
| notify | { type: string, text: string } | 通知事件,包含通知类型和内容 |
| remote-user-status-change | { userId: string, action: string, userList: string[] } | 远端用户进入/离开房间事件,action 为 'enter' 或 'exit' |
| action | (type: string, payload: object) | 统一操作事件,type 为动作常量,payload 为对应数据(详见下方) |
action 事件常量
| 常量 | payload | 说明 |
|------|---------|------|
| ROOM_ENTER | { roomId, userId } | 进入房间 |
| ROOM_EXIT | { roomId, userId } | 离开房间 |
| REMOTE_USER_ENTER | { userId, userList } | 远端用户进入房间 |
| REMOTE_USER_EXIT | { userId, userList } | 远端用户离开房间 |
| MICROPHONE_CHANGE | { enabled } | 麦克风开启/关闭,enabled 为 true 表示开启 |
| CAMERA_CHANGE | { enabled } | 摄像头开启/关闭,enabled 为 true 表示开启 |
| CAMERA_SWITCH | { front, cameraId } | 切换前后摄像头,front 为 true 表示前置摄像头 |
| CALL_START | { from, targets } | 发起呼叫,targets 为目标用户 ID 数组 |
| CALL_ACCEPT | { from, target } | 接听来电,target 为呼叫方 |
| CALL_REJECT | { from, caller } | 拒绝来电,caller 为呼叫方 |
| CALLEE_ACCEPT | { from, callee } | 被叫方接听呼叫,callee 为被叫方 |
| CALLEE_REJECT | { from, callee } | 被叫方拒绝呼叫,callee 为被叫方 |
| PEER_HANGUP | { from, peer } | 对方挂断通话,peer 为挂断方 |
| CALL_END | { from, targets } | 本端挂断通话,targets 为通话目标数组 |
| INVITE_SEND | { from, target } | 邀请用户加入通话,target 为被邀请用户 |
| INVITE_ACCEPT | { from, inviter } | 接受邀请,inviter 为邀请方 |
| INVITE_REJECT | { from, inviter } | 拒绝邀请,inviter 为邀请方 |
| MINIMIZE | 无 | 最小化通话窗口 |
| MAXIMIZE | 无 | 最大化通话窗口 |
| VIDEO_PROFILE_CHANGE | { profile } | 视频分辨率变化,profile 如 '1080p' |
| NETWORK_QUALITY_CHANGE | { quality, report } | 网络质量变化,quality 为 'poor' 或 'good' |
| ERROR | { action, message } | 操作异常,action 为出错动作,message 为错误信息 |
功能特性
- 音视频通话:仅支持一对多音视频通话
- 网络质量监测:实时监测网络质量,动态调整视频质量
- 设备控制:支持开启/关闭麦克风、摄像头
- 扬声器控制:支持切换扬声器/听筒
- 动态视频质量调整:根据网络状况自动调整视频分辨率
注意事项
- 使用前需获取腾讯云 TRTC 服务的 SDK App ID 和 SDK 密钥
- 生产环境建议使用服务端返回
userSig的方式,避免将sdkSecretKey暴露在前端代码中 - 确保在 HTTPS 环境下使用,否则可能无法正常访问摄像头和麦克风
- 首次使用时需要用户授权摄像头和麦克风权限
