4.8.2 • Published 23h ago
@xkit-yx/call-kit
Licence
MIT
Version
4.8.2
Deps
2
Size
644 kB
Vulns
0
Weekly
0
网易云信 呼叫组件 API
基于云信 NIM V10 SDK(nim-web-sdk-ng)封装的一对一音视频呼叫组件,支持 Web 端和微信小程序端。
构建与验证平台
- 对外构建入口:
cd Electron && npm run build:web-callkit-sdk - 正式发布产物命令:
cd Electron && npm run package:web-callkit-sdk - 正式产物输出:
Electron/out/release-artifacts/xkit-yx-call-kit-<version>.tgz - 包内实现入口:
cd Web/call-kit && npm run build - 验证平台:
Web/basic-react与Web/basic-vue3 - 当前正式
build会生成dist/、dist/types/与miniprogram_dist/
正式 consumer 安装矩阵
如果你只接 SDK,不接 UIKit,正式 consumer 只需要:
@xkit-yx/call-kitnim-web-sdk-ngnertc-web-sdk
如果你要接 UIKit,则在上面基础上再加对应 UIKit 包和框架 peer:
- React:
@xkit-yx/call-kit-react-ui+react+react-dom - Vue3:
@xkit-yx/call-kit-vue3-ui+vue
registry 安装示意:
npm install @xkit-yx/call-kit nim-web-sdk-ng nertc-web-sdk
npm install @xkit-yx/call-kit-react-ui react react-dom
tarball 安装示意:
npm install ./xkit-yx-call-kit-<version>.tgz nim-web-sdk-ng nertc-web-sdk
补充说明:
- 当前正式包不要求客户理解 monorepo 内部
packages/callkit-*结构 - 当前正式包不要求客户必须使用
umi或vite Web/basic-react/Web/basic-vue3的宿主框架,只属于 example 验证平台自身要求
正式包边界与 example 宿主边界
这份 SDK README 只描述正式对外 contract:
@xkit-yx/call-kit的安装要求setup/call/accept/hangup等运行 API- 与 UIKit 配合时的最小接入链路
它不把以下内容当作正式接入门槛:
Web/basic-react的umi宿主要求Web/basic-vue3的vite宿主要求- demo shell、调试面板和 example 页面结构
如果客户只是把 SDK 接到自己原有 Web 项目里,不需要复制 example 的宿主工程结构。
验证平台
Web/basic-react:React consumer 验证平台Web/basic-vue3:Vue3 consumer 验证平台
这些 example 的平台差异审核基线,统一以
specs/002-electron-callkit/contracts/electron-web-example-platform-baseline.md
为准。当前固定口径是:
- Web example 只按
external路径审核,不承担managed模式切换 - Web example 保留群呼验证路径
- 与 Electron 对齐时,只把
external和 1v1 主链路视为 shared 交集能力 - Electron example 支持默认
external+ 可切换managed,且群呼入口必须禁用并明确标注“暂不支持”
这两个 example 的职责是:
- 验证正式
sdk/uikit的 tarball / registry 接入链路 - 作为发布前“低门槛接入”的样板
- 不再直连仓库内部 shared source 路径
Phase D 最小接入
Web 正式接入现在固定成两层:
- 宿主先完成
nim-web-sdk-ng登录,再通过NECall.getInstance().setup({ nim, appkey, ... })接入@xkit-yx/call-kit - 页面层再组合 React 或 Vue3 的 UIKit
CallViewProvider
补充说明:
- Web 没有 Electron
host-helper这一层 - demo-shell 属于 example host 层,不是正式接入必需项
- 当前 Web demo 页面只剩登录/登出、runtime/provider 装配与少量参数转换;这些页面壳层不属于 shared formal contract
Web 端(单呼)
初始化
import NIM from 'nim-web-sdk-ng'
import { NECall } from '@xkit-yx/call-kit'
const nim = NIM.getInstance({
appkey: 'your_appkey',
apiVersion: 'v2',
debugLevel: 'debug',
})
await nim.V2NIMLoginService.login('accountId', 'token')
// 使用单例模式获取实例
const neCall = NECall.getInstance()
neCall.setup({
nim, // NIM V10 实例
appkey: 'your_appkey',
debug: true,
})
setup 参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
nim |
V2NIM |
必填 | NIM V10 实例 |
appkey |
string |
必填 | 应用 AppKey |
rtcContext |
RTCConfig |
- | RTC 私有化配置 |
enableRecord |
boolean |
true |
是否开启话单 |
enableAutoJoinSignalChannel |
boolean |
false |
被叫是否自动加入信令频道 |
enableJoinRtcWhenCall |
boolean |
false |
主叫发起呼叫时是否提前加入 RTC |
enableSubscribeVideoInAudioCall |
boolean |
false |
音频通话时是否也订阅对端视频,常用于接收音频通话中的视频占位图 |
debug |
boolean |
false |
是否打印日志 |
主叫方
// 发起呼叫
await neCall.call({
accId: 'calleeAccId', // 被叫 accountId
callType: '2', // '1': 音频, '2': 视频
})
// 设置本地/远端视频视图(仅 Web 端)
const localView = document.getElementById('localView')
const remoteView = document.getElementById('remoteView')
neCall.setLocalView(localView)
neCall.setRemoteView(remoteView)
// 取消呼叫
await neCall.hangup()
被叫方
// 监听来电邀请
neCall.on('onReceiveInvited', (info) => {
// info.callerAccId: 主叫 accountId
// info.callType: '1' | '2'
// info.channelId: 信令频道 id(挂断时使用)
// info.extraInfo: 扩展信息(透传)
console.log('来电:', info)
})
// 接听
await neCall.accept()
// 拒绝
await neCall.hangup()
通话中(主叫 & 被叫)
// 通话建立成功
neCall.on('onCallConnected', (callInfo) => {
console.log('通话已建立', callInfo)
})
// 开关本地视频
await neCall.enableLocalVideo(true) // 开启
await neCall.enableLocalVideo(false) // 关闭
// 开关本地音频
await neCall.enableLocalAudio(true) // 开启
await neCall.enableLocalAudio(false) // 关闭(静音)
// 切换通话类型(音频 ↔ 视频)
await neCall.switchCallType({ callType: '1', state: 1 }) // 发起切换请求
// state: 1=邀请, 2=同意, 3=拒绝
// 监听对端切换通话类型
neCall.on('onCallTypeChange', ({ callType, state }) => {
// callType: '1' | '2'
// state: 1=邀请, 2=同意, 3=拒绝
})
// 挂断
await neCall.hangup()
通话结束
neCall.on('onCallEnd', ({ reasonCode, extraString }) => {
// reasonCode 含义:
// 2 - 呼叫超时(自动挂断)
// 3 - 对方正在通话中(占线)
// 11 - 本地主动取消呼叫
// 12 - 对端主动取消呼叫
// 13 - 本地主动拒绝
// 14 - 对端主动拒绝
// 15 - 本地主动挂断
// 16 - 对端挂断 / RTC 房间关闭
// 17 - 多端登录:其他端已拒绝
// 18 - 多端登录:其他端已接听
// 20 - 对端 RTC 断开(网络异常)
})
// 话单发送回调(通话结束时组件自动发送话单消息给对端)
neCall.on('onRecordSend', (record) => {
// 可在此更新本端通话记录 UI
console.log('话单已发送', record)
})
其他方法
// 设置呼叫超时(秒)
neCall.setTimeout(30)
// 动态配置
neCall.setCallConfig({
enableOffline: true, // 是否支持离线推送
enableSwitchVideoConfirm: false, // 切换视频是否需要对端确认
enableSwitchAudioConfirm: false, // 切换音频是否需要对端确认
enableJoinRtcWhenCall: false, // 主叫是否提前加入 RTC
enableSubscribeVideoInAudioCall: false, // 音频通话时也订阅对端视频
})
// 获取当前配置
const config = neCall.getCallConfig()
// 获取通话信息(通话中有效)
const callInfo = neCall.getCallInfo()
// callInfo.callStatus: 0=闲置 1=呼叫中 2=被叫中 3=通话中
// callInfo.callType: 1=音频 2=视频
// callInfo.callerInfo / calleeInfo: { accId, uid }
// IM 断线重连后调用(触发离线消息重新拉取)
neCall.reconnect()
// 销毁实例(退出登录时调用)
neCall.destroy()
音频通话视频占位图(仅 Web)
// 接收端需要开启订阅,才能在音频通话中收到对端发送的视频占位流
neCall.setup({
nim,
appkey: 'your_appkey',
enableSubscribeVideoInAudioCall: true,
})
// 在发起/接听前设置音频通话占位图
await neCall.setAudioCallPlaceholderImage({
url: 'https://your-cdn.example.com/call-placeholder.png',
layout: {
objectFit: 'contain',
backgroundColor: '#000000',
width: 1280,
height: 720,
fps: 5,
},
})
setAudioCallPlaceholderImage 会把远端图片绘制到 canvas,再通过 captureStream() 生成 MediaStreamTrack,在音频通话时作为自定义视频流推送到房间里。接收端只有在 enableSubscribeVideoInAudioCall 为 true 时才会订阅这路视频;否则音频通话仍然只订阅音频。
当前版本要求在音频通话建连前调用该方法,已建立的音频通话暂不支持热切换占位视频源。
微信小程序端
小程序端使用独立构建产物,通过 live-pusher(本地推流)和 live-player(远端拉流)实现音视频通话。
初始化
// App.vue / app.js
import V2NIM from 'nim-web-sdk-ng/dist/v2/NIM_MINIAPP_SDK'
import { NECall } from '@xkit-yx/call-kit/miniprogram_dist/index'
const nim = V2NIM.getInstance({
appkey: 'your_appkey',
apiVersion: 'v2',
debugLevel: 'debug',
})
await nim.V2NIMLoginService.login(accountId, token)
const neCall = NECall.getInstance()
neCall.setup({
nim,
appkey: 'your_appkey',
debug: true,
})
// 监听来电(建议在 App 全局注册,防止重复注册)
neCall.off('onReceiveInvited')
neCall.on('onReceiveInvited', () => {
const pages = getCurrentPages()
const isOnCallPage = pages.some((p) => p.route === 'pages/call/call')
if (!isOnCallPage) {
wx.navigateTo({ url: '/pages/call/call' })
}
})
小程序端特有事件
| 事件 | 回调参数 | 说明 |
|---|---|---|
onStreamPublish |
url: string |
本地推流地址(赋值给 live-pusher 的 url) |
onStreamSubscribed |
url: string |
远端拉流地址(赋值给 live-player 的 src) |
onVideoMuteOrUnmute |
mute: boolean |
对端关闭/开启摄像头 |
live-player mode 适配(鸿蒙系统)
鸿蒙系统的 live-player 需要使用 'live' 模式,其他平台使用 'RTC':
function getPlayerMode() {
try {
const info = wx.getDeviceInfo ? wx.getDeviceInfo() : wx.getSystemInfoSync()
const platform = (info.platform || '').toLowerCase()
const system = (info.system || '').toLowerCase()
if (platform.includes('ohos') || system.includes('harmonyos')) {
return 'live'
}
} catch (e) {}
return 'RTC'
}
事件列表(完整)
| 事件名 | 回调参数 | 适用平台 | 说明 |
|---|---|---|---|
onReceiveInvited |
NEInviteInfo |
Web / 小程序 | 收到来电邀请 |
onCallConnected |
NECallInfo | undefined |
Web / 小程序 | 通话建立成功 |
onCallEnd |
NECallEndInfo |
Web / 小程序 | 通话结束 |
onCallTypeChange |
NECallTypeChangeInfo |
Web / 小程序 | 通话类型切换 |
onRecordSend |
SignalControllerCallRecord |
Web / 小程序 | 话单发送完成 |
onStreamPublish |
url: string |
小程序 | 本地推流地址就绪 |
onStreamSubscribed |
url: string |
小程序 | 远端拉流地址就绪 |
onVideoMuteOrUnmute |
mute: boolean |
小程序 | 对端摄像头开关变化 |
onCallEnd reasonCode 含义
| code | 说明 |
|---|---|
| 2 | 呼叫超时,自动挂断 |
| 3 | 对方正在通话中(占线) |
| 11 | 本地主动取消呼叫 |
| 12 | 对端主动取消呼叫 |
| 13 | 本地主动拒绝 |
| 14 | 对端主动拒绝 |
| 15 | 本地主动挂断 |
| 16 | 对端挂断 / RTC 房间关闭 |
| 17 | 多端登录:其他端已拒绝 |
| 18 | 多端登录:其他端已接听 |
| 20 | 对端 RTC 断开(网络异常) |
类型定义
type NESetupConfig = {
nim: V2NIM // NIM V10 实例
appkey: string // 应用 AppKey
rtcContext?: RTCConfig // RTC 私有化配置
enableRecord?: boolean // 是否开启话单,默认 true
enableAutoJoinSignalChannel?: boolean // 被叫是否自动加入信令,默认 false
enableJoinRtcWhenCall?: boolean // 主叫是否提前加入 RTC,默认 false
enableSubscribeVideoInAudioCall?: boolean // 音频通话时也订阅对端视频,常用于视频占位图
debug?: boolean // 打印日志,默认 false
}
type NECallParam = {
accId: string // 被叫 accountId
callType: '1' | '2' // '1': 音频, '2': 视频
extraInfo?: string // 透传到被叫 onReceiveInvited
rtcChannelName?: string // 自定义 RTC 频道名
globalExtraCopy?: string // 服务端抄送自定义信息
pushConfig?: SignalControllerPushInfo // 离线推送配置
}
type NEHangupParam = {
reason?: number // 挂断原因码
extraString?: string // 附加信息
channelId?: string // 信令频道 id
duration?: number // 通话时长(秒),用于话单生成
}
type NECallConfig = {
enableOffline?: boolean // 支持离线推送,默认 true
enableSwitchVideoConfirm?: boolean // 切换视频需对端确认,默认 false
enableSwitchAudioConfirm?: boolean // 切换音频需对端确认,默认 false
enableJoinRtcWhenCall?: boolean // 主叫提前加入 RTC,默认 false
enableSubscribeVideoInAudioCall?: boolean // 音频通话时也订阅对端视频,常用于视频占位图
}
type NECallPlaceholderImageObjectFit =
| 'contain'
| 'cover'
| 'fill'
| 'none'
| 'scale-down'
type NEAudioCallPlaceholderImageLayout = {
objectFit?: NECallPlaceholderImageObjectFit // 图片布局方式,默认 contain
backgroundColor?: string // 画布背景色,默认 #000000
width?: number // 输出宽度,默认 1280
height?: number // 输出高度,默认 720
fps?: number // canvas 重绘帧率,默认 5
}
type NEAudioCallPlaceholderImageConfig = {
url: string // 占位图地址
layout?: NEAudioCallPlaceholderImageLayout
}
type NEInviteInfo = {
callerAccId: string // 主叫 accountId
callType: '1' | '2' // 通话类型
extraInfo?: string // 透传信息
channelId: string // 信令频道 id
}
type NECallEndInfo = {
reasonCode: number // 结束原因码(见上表)
extraString?: string // 附加信息
message?: string // 结束消息
}