npm.io
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-reactWeb/basic-vue3
  • 当前正式 build 会生成 dist/dist/types/miniprogram_dist/

正式 consumer 安装矩阵

如果你只接 SDK,不接 UIKit,正式 consumer 只需要:

  • @xkit-yx/call-kit
  • nim-web-sdk-ng
  • nertc-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-* 结构
  • 当前正式包不要求客户必须使用 umivite
  • Web/basic-react / Web/basic-vue3 的宿主框架,只属于 example 验证平台自身要求

正式包边界与 example 宿主边界

这份 SDK README 只描述正式对外 contract:

  • @xkit-yx/call-kit 的安装要求
  • setup/call/accept/hangup 等运行 API
  • 与 UIKit 配合时的最小接入链路

它不把以下内容当作正式接入门槛:

  • Web/basic-reactumi 宿主要求
  • Web/basic-vue3vite 宿主要求
  • 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 正式接入现在固定成两层:

  1. 宿主先完成 nim-web-sdk-ng 登录,再通过 NECall.getInstance().setup({ nim, appkey, ... }) 接入 @xkit-yx/call-kit
  2. 页面层再组合 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,在音频通话时作为自定义视频流推送到房间里。接收端只有在 enableSubscribeVideoInAudioCalltrue 时才会订阅这路视频;否则音频通话仍然只订阅音频。

当前版本要求在音频通话建连前调用该方法,已建立的音频通话暂不支持热切换占位视频源。


微信小程序端

小程序端使用独立构建产物,通过 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-pusherurl
onStreamSubscribed url: string 远端拉流地址(赋值给 live-playersrc
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                    // 结束消息
}