Electron(桌面) 快速开始与功能指南
Electron(桌面):前置条件、安装、Token、加入频道、发布订阅、事件、错误处理与排障;接口名与签名在构建时取自公开 SDK 接口定义。
Electron 端 = Web SDK 的公开面 + 桌面采集桥。渲染进程里用的还是 @nimeet/rtc-web 那套 AgoraRTC 形状的 API,桌面端额外提供「枚举窗口/屏幕源」和「source-pinned 屏幕共享」,因为浏览器的 getDisplayMedia 无法指定某个窗口。
前置条件
- Node.js ≥ 22、pnpm 10;Electron ≥ 28(主进程/预加载/渲染进程三处都要改)。
contextIsolation: true(默认)。本 SDK 的桌面桥通过预加载脚本注入,不依赖nodeIntegration。- 已拿到
appId和一个能签发 Token 的后端(见 控制台开通服务与获取凭证)。
安装
pnpm add @nimeet/sdk-electron @nimeet/rtc-web三个入口分别给三处用:@nimeet/sdk-electron/main(主进程)、@nimeet/sdk-electron/preload(预加载)、@nimeet/sdk-electron(渲染进程/通用工具)。
获取 Token
与 Web 完全相同:Token 由你的后端调 POST /v1/tokens 签发,渲染进程只拿 { appId, rtcToken, rtcUid }。 主进程也不要持有 customerSecret——它会出现在崩溃转储和用户可读的文件系统里。
加入频道
主进程注册桌面桥:
// main.js(主进程)
const { app, BrowserWindow, ipcMain, desktopCapturer } = require('electron');
const { registerDesktopBridge } = require('@nimeet/sdk-electron/main');
app.whenReady().then(() => {
registerDesktopBridge({ ipcMain, desktopCapturer });
});预加载脚本暴露最小面:
// preload.js(预加载)
const { contextBridge, ipcRenderer } = require('electron');
const { exposeDesktopBridge } = require('@nimeet/sdk-electron/preload');
exposeDesktopBridge(contextBridge, ipcRenderer);渲染进程里用 Web 形状的客户端入会(与 Web 端同一套方法):
import AgoraRTC from '@nimeet/rtc-web';
const client = AgoraRTC.createClient({ mode: 'rtc', codec: 'vp8' });
client.on('user-published', async (user, mediaType) => {
await client.subscribe(user, mediaType);
if (mediaType === 'video') user.videoTrack?.play('remote-player');
});
const uid = await client.join(appId, 'weekly-sync', rtcToken, rtcUid);发布与订阅
摄像头/麦克风与 Web 一致;屏幕共享用「源」而不是默认选择器:
import { pickPrimarySource, groupSources } from '@nimeet/sdk-electron';
// window.nimeetDesktop 由预加载脚本注入
const sources = await window.nimeetDesktop.getSources({ types: ['screen', 'window'] });
const groups = groupSources(sources); // 按「屏幕 / 窗口」分组,供下拉框使用
const primary = pickPrimarySource(sources); // 主屏,作为默认值
const screenTrack = await AgoraRTC.createScreenVideoTrack(
{ electronScreenSourceId: primary.id },
'disable',
);
await client.publish(screenTrack);electronScreenSourceId 是「钉住的源」(字段名来自 ScreenVideoTrackInitConfig):共享期间不会因为用户切换窗口而变——这是桌面端与浏览器 getDisplayMedia 最重要的区别。
事件
与 Web 相同的事件集(user-joined / user-published / connection-state-change / token-privilege-will-expire / network-quality…), 另外桌面桥额外提供两个通道事件(前缀 nimeet:desktop:):
| 事件 | 方向 | 用途 |
|---|---|---|
nimeet:desktop:sources | 渲染 ← 主 | 返回屏幕/窗口源列表 |
nimeet:desktop:capture-permission | 渲染 ← 主 | macOS 屏幕录制权限状态(Chromium 不暴露该状态) |
错误处理
常见错误来源有三类,建议分开处理:
- RTC 错误:与 Web 相同的
AgoraRTCError(code字符串); - IPC 错误:主进程未注册桥 → 渲染进程
window.nimeetDesktop为undefined;预加载未注入 → 同上; - 系统权限:macOS「屏幕录制」未授权时,源列表为空而不是报错。
const sources = await window.nimeetDesktop?.getSources({ types: ['screen'] }) ?? [];
if (sources.length === 0) {
// macOS:引导用户去 系统设置 → 隐私与安全性 → 屏幕录制 勾选本应用后重启
}排障
| 现象 | 原因 | 处理 |
|---|---|---|
window.nimeetDesktop 是 undefined | 预加载未执行 / contextIsolation 配置不符 | 检查 webPreferences.preload 路径与打包后文件是否存在 |
| 源列表为空(macOS) | 屏幕录制权限未授予 | 系统设置授权后重启应用(权限变更不会热生效) |
| 共享出黑屏 | 选中的是受保护的窗口(如 DRM 播放器) | 改用整屏源 |
渲染进程报 require is not defined | 用了 CommonJS 写法 | 渲染进程按 ESM 写,或让打包器处理 |
打包后 IPC 报 No handler registered | 主进程注册桥的代码被 tree-shaking 掉 | 在主进程入口显式调用 registerDesktopBridge |
下一步
平台信息
| 项 | 值 |
|---|---|
| 平台 | Electron(桌面) |
| 语言 | TypeScript |
| SDK 包 | @nimeet/sdk-electron |
| 接口版本 | 4.6.2 |
| 兼容参考 | 声网 electron 端 API |
本端公开接口与声网 electron 端 API 对齐;安装与运行要求见上面前置条件。
可用性
该端 SDK:正式可用。本页代码片段使用公开 API;运行时的能力边界以实际发布的 SDK 包为准。
加入频道链路
下列方法名、参数类型与返回值与同端 API 参考一致。一个平台若有多个重载,这里展示最常用的那一个;完整重载见同端 API 参考页。
| 步骤 | 接口 | 方法 | 重载数 |
|---|---|---|---|
| 订阅远端媒体 | IBaseSpatialAudioEngine | muteRemoteAudioStream | 1 |
| 创建实例 / 初始化 | ILocalSpatialAudioEngine | initialize | 1 |
| 续期 Token | IMusicContentCenter | renewToken | 1 |
| 开启视频与本地预览 | IRtcEngine | enableVideo | 1 |
| 加入频道 | IRtcEngine | joinChannel | 1 |
| 离开频道 | IRtcEngine | leaveChannel | 1 |
| 频道场景 | IRtcEngine | setChannelProfile | 1 |
| 切换角色 | IRtcEngine | setClientRole | 1 |
| 绑定本地画面 | IRtcEngine | setupLocalVideo | 1 |
muteRemoteAudioStream(uid: number, mute: boolean): number
initialize(): number
renewToken(token: string): number
enableVideo(): number
joinChannel(token: string, channelId: string, uid: number, options: ChannelMediaOptions): number
leaveChannel(options?: LeaveChannelOptions): number
setChannelProfile(profile: ChannelProfileType): number
setClientRole(role: ClientRoleType, options?: ClientRoleOptions): number
setupLocalVideo(canvas: VideoCanvas): number事件与回调
回调注册在 IRtcEngineEventHandler 上;不同端的命名可能不同,完整清单见同端 API 参考页。
| 回调 | 签名 | 用途 |
|---|---|---|
onConnectionStateChanged | onConnectionStateChanged(connection: RtcConnection, state: ConnectionStateType, reason: ConnectionChangedReasonType): void | 入会结果 / 连接状态变化:据此更新 UI 并决定是否重连。 |
onUserJoined | onUserJoined(connection: RtcConnection, remoteUid: number, elapsed: number): void | 远端用户加入:分配画面容器。 |
onFirstRemoteVideoDecoded | onFirstRemoteVideoDecoded(connection: RtcConnection, remoteUid: number, width: number, height: number, elapsed: number): void | 远端视频状态:首帧解码后即可渲染。 |
onUserOffline | onUserOffline(connection: RtcConnection, remoteUid: number, reason: UserOfflineReasonType): void | 远端用户离开:释放容器与订阅。 |
onRequestToken | onRequestToken(connection: RtcConnection): void | Token 即将过期:调用服务端续期后 `renewToken`。 |
onError | onError(err: ErrorCodeType, msg: string): void | 运行时错误:`err` 为声网错误号,按错误码表分流。 |
onNetworkQuality | onNetworkQuality(connection: RtcConnection, remoteUid: number, txQuality: QualityType, rxQuality: QualityType): void | 网络质量:上报到水晶球或提示用户。 |
错误码
| 常量 | 值 |
|---|---|
ErrAborted | 20 |
ErrAdmGeneralError | 1005 |
ErrAdmInitPlayout | 1008 |
ErrAdmInitRecording | 1011 |
ErrAdmStartPlayout | 1009 |
ErrAdmStartRecording | 1012 |
ErrAdmStopPlayout | 1010 |
ErrAdmStopRecording | 1013 |
ErrAlreadyInUse | 19 |
ErrBitrateLimit | 115 |
ErrBufferTooSmall | 6 |
ErrCertRaw | 1001 |
ErrClientIsBannedByServer | 123 |
ErrConnectionInterrupted | 111 |
ErrConnectionLost | 112 |
ErrDatastreamDecryptionFailed | 122 |
ErrDecryptionFailed | 120 |
ErrEncryptedStreamNotAllowedPublish | 130 |
ErrFailed | 1 |
ErrInitNetEngine | 21 |
ErrInvalidAppId | 101 |
ErrInvalidArgument | 2 |
ErrInvalidChannelName | 102 |
ErrInvalidState | 8 |
ErrInvalidToken | null |
ErrInvalidUserAccount | 134 |
ErrInvalidUserId | 121 |
ErrJoinChannelRejected | 17 |
ErrLeaveChannelRejected | 18 |
ErrNoPermission | 9 |
ErrNoServerResources | 103 |
ErrNotInChannel | 113 |
ErrNotInitialized | 7 |
ErrNotReady | 3 |
ErrNotSupported | 4 |
ErrOk | 0 |
ErrPcmsendBufferoverflow | 201 |
ErrPcmsendFormat | 200 |
ErrRefused | 5 |
ErrResourceLimited | 22 |
ErrSetClientRoleNotAuthorized | 119 |
ErrSizeTooLarge | 114 |
ErrStreamMessageTimeout | 117 |
ErrTimedout | 10 |
ErrTokenExpired | 109 |
ErrTooManyDataStreams | 116 |
ErrVdmCameraNotAuthorized | 1501 |
IRtcEngine | null |
平台 REST 层用 REST 错误码;SDK 层把连接/鉴权失败映射成声网错误号,映射表见 各端 SDK 错误标识。