Web(浏览器) 快速开始与功能指南
Web(浏览器):前置条件、安装、Token、加入频道、发布订阅、事件、错误处理与排障;接口名与签名在构建时取自公开 SDK 接口定义。
浏览器端提供与声网 Web SDK 4.24.9 一致的公开 API:142 个方法、153 个重载、47 个事件与 96 个错误标识。下面的方法链覆盖创建客户端、加入频道、发布订阅到离开的完整过程,可直接在项目中复用。
前置条件
- Node.js ≥ 22(构建/测试用)、pnpm 10;
pnpm install。 - 浏览器:Chrome 65+、Edge、Firefox 69+、Safari 11+(屏幕共享需 Safari 13+)。
- 采集音视频要求安全上下文:
https://或http://localhost;纯http://局域网 IP 下getUserMedia会直接失败。 - 已按 控制台开通服务与获取凭证 拿到
appId,并有一个能签发 Token 的后端。
安装
pnpm add @nimeet/rtc-web代码库中开发时用工作区包:pnpm --filter @nimeet/rtc-web build。版本与兼容版本对齐(公开 SDK 接口定义 的 apiVersion 与接口定义小节一致)。
获取 Token
浏览器不能持有 customerSecret。正确链路是你的后端调控制面:
// 你的后端(Node 示例):
// POST $NIMEET_API_BASE/v1/tokens { channelName, uid, role, ttlSeconds }
// 前端只拿到 { appId, rtcToken, rtcUid }
const response = await fetch('/api/rtc-token?channel=weekly-sync&uid=10001');
const { appId, rtcToken, rtcUid } = await response.json();Token 语义(权限位、exp/pexp 分离、通配)见 AccessToken2 语义。
加入频道
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');
if (mediaType === 'audio') user.audioTrack?.play();
});
client.on('token-privilege-will-expire', async () => {
const fresh = await fetch('/api/rtc-token?renew=1').then((r) => r.json());
await client.renewToken(fresh.rtcToken);
});
const uid = await client.join(appId, 'weekly-sync', rtcToken, rtcUid);页面需要两个容器:<div id="local-player"></div> 与 <div id="remote-player"></div>。媒体边缘地址按 Token 载荷 surl → window.__NIMEET_SFU_URL__ → 同源 /rtc 的顺序解析。
发布与订阅
const [microphoneTrack, cameraTrack] = await AgoraRTC.createMicrophoneAndCameraTracks();
cameraTrack.play('local-player');
await client.publish([microphoneTrack, cameraTrack]);
// 手动订阅(默认是自动订阅,关掉后需要自己 subscribe)
await client.unsubscribe(remoteUser, 'video');
cameraTrack.setEnabled(false); // 静音画面但保持发布轨道是对象:发布的是 ILocalTrack 实例,不是「资源 id」。停止发布要 client.unpublish(track),释放采集要 track.close()。
事件
| 事件 | 何时触发 | 典型处理 |
|---|---|---|
user-joined / user-left | 远端用户进出 | 分配/释放画面容器 |
user-published / user-unpublished | 远端发布/取消发布 | subscribe 后播放或停止 |
connection-state-change | DISCONNECTED/CONNECTING/CONNECTED/RECONNECTING | RECONNECTING 时不要销毁轨道 |
token-privilege-will-expire | 权限到期前 30 秒 | 续期并 renewToken |
network-quality | 周期上报上行/下行质量 | 提示弱网或上报水晶球 |
stream-message | 数据通道消息 | 白板/信令载荷 |
exception | SDK 内部异常 | 收集日志,按 code 分流 |
错误处理
失败统一是 AgoraRTCError:{ code, message, data? },code 是声网 4.x 的字符串标识(INVALID_PARAMS、PERMISSION_DENIED、TOKEN_EXPIRE…),96 项全表见同端 API 参考。平台 REST 错误码另有 sdkCode(负数错误号),映射见 各端 SDK 错误标识。
try {
await client.publish([microphoneTrack, cameraTrack]);
} catch (error) {
if (error.code === 'PERMISSION_DENIED') {
// 用户拒绝了摄像头/麦克风权限
} else if (error.code === 'NOT_SUPPORTED') {
// 该能力在当前媒体栈未实现(例如 E2EE),不要静默忽略
}
}排障
| 现象 | 原因 | 处理 |
|---|---|---|
getUserMedia 直接抛错 | 非安全上下文或权限被拒 | 用 https:// 或 localhost;检查浏览器站点权限 |
入会报 INVALID_TOKEN | Token 与 appId/频道不匹配,或已过期 | GET /v1/tokens/verify?token=&channelName= 自查 |
| 有声音没画面 | 远端视频轨道未 subscribe 或容器 id 写错 | 检查 user-published 回调里是否 play('remote-player') |
| Safari 无法采集 | 需要用户手势触发 | 把采集放在按钮点击事件里 |
一直 RECONNECTING | 媒体边缘不可达 | 检查 SFU 地址解析链与反向代理 WebSocket 升级 |
| 打包体积过大 | 未做 tree-shaking | 参考声网的按需引入思路,import { AgoraRTC } from '@nimeet/rtc-web' |
下一步
平台信息
| 项 | 值 |
|---|---|
| 平台 | Web(浏览器) |
| 语言 | TypeScript |
| SDK 包 | @nimeet/rtc-web |
| 接口版本 | 4.24.9 |
| 兼容参考 | 声网 javascript 端 API |
本端公开接口与声网 javascript 端 API 对齐;安装与运行要求见上面前置条件。
可用性
该端 SDK:正式可用。本页代码片段使用公开 API;运行时的能力边界以实际发布的 SDK 包为准。
加入频道链路
下列方法名、参数类型与返回值与同端 API 参考一致。一个平台若有多个重载,这里展示最常用的那一个;完整重载见同端 API 参考页。
| 步骤 | 接口 | 方法 | 重载数 |
|---|---|---|---|
| 加入频道 | IAgoraRTCClient | join | 1 |
| 离开频道 | IAgoraRTCClient | leave | 1 |
| 发布本地轨道 | IAgoraRTCClient | publish | 3 |
| 续期 Token | IAgoraRTCClient | renewToken | 1 |
| 切换角色 | IAgoraRTCClient | setClientRole | 1 |
| 订阅远端媒体 | IAgoraRTCClient | subscribe | 4 |
| 创建实例 / 初始化 | IAgoraRTC | createClient | 1 |
join(appid: string, channel: string, token: string|null, uid?: UID|null, options?: {autoSubscribe?:boolean;autoReceiveAndPlayAudio?:boolean;networkQualityProbe?:boolean}): Promise<UID>
leave(): Promise<void>
publish(tracks: ILocalTrack|ILocalTrack[]): Promise<void>
renewToken(token: string): Promise<void>
setClientRole(role: ClientRole, options?: ClientRoleOptions): Promise<void>
subscribe(user: IAgoraRTCRemoteUser|UID, mediaType: "video"): Promise<IRemoteVideoTrack>
createClient(config: ClientConfig): IAgoraRTCClient事件与回调
| 接口 | 事件名 | 监听器参数 |
|---|---|---|
ILocalTrack | track-ended | () |
ILocalTrack | track-updated | track:MediaStreamTrack |
ILocalTrack | transceiver-updated | transceiver:RTCRtpTransceiver,type?:StreamType |
ILocalVideoTrack | track-ended | () |
ILocalVideoTrack | track-updated | track:MediaStreamTrack |
ILocalVideoTrack | transceiver-updated | transceiver:RTCRtpTransceiver,type?:StreamType |
ILocalVideoTrack | video-element-visible-status | data?:CheckVideoVisibleResult |
IRemoteTrack | first-frame-decoded | () |
IRemoteTrack | track-updated | track:MediaStreamTrack |
IRemoteTrack | transceiver-updated | transceiver:RTCRtpTransceiver |
IRemoteVideoTrack | first-frame-decoded | () |
IRemoteVideoTrack | sei-received | sei:Uint8Array,rtpTimestamp?:number |
IRemoteVideoTrack | track-updated | track:MediaStreamTrack |
IRemoteVideoTrack | transceiver-updated | transceiver:RTCRtpTransceiver |
IRemoteVideoTrack | video-element-visible-status | data?:CheckVideoVisibleResult |
IRemoteVideoTrack | video-state-changed | videoState:VideoState |
IAgoraRTCClient | channel-media-relay-event | event:ChannelMediaRelayEvent |
IAgoraRTCClient | channel-media-relay-state | state:ChannelMediaRelayState,code:ChannelMediaRelayError |
IAgoraRTCClient | connection-state-change | curState:ConnectionState,revState:ConnectionState,reason?:ConnectionDisconnectedReason |
IAgoraRTCClient | crypt-error | () |
IAgoraRTCClient | exception | event:{code:number;msg:string;uid:UID} |
IAgoraRTCClient | is-using-cloud-proxy | isUsingProxy:boolean |
IAgoraRTCClient | join-fallback-to-proxy | proxyServer:string |
IAgoraRTCClient | live-streaming-error | url:string,err:AgoraRTCError |
IAgoraRTCClient | live-streaming-warning | url:string,warning:AgoraRTCError |
IAgoraRTCClient | media-reconnect-end | uid:UID |
IAgoraRTCClient | media-reconnect-start | uid:UID |
IAgoraRTCClient | network-quality | stats:NetworkQuality |
IAgoraRTCClient | peerconnection-state-change | curState:RTCPeerConnectionState,revState:RTCPeerConnectionState |
IAgoraRTCClient | published-user-list | users:IAgoraRTCRemoteUser[] |
IAgoraRTCClient | stream-fallback | uid:UID,isFallbackOrRecover:"fallback"|"recover" |
IAgoraRTCClient | stream-message | uid:UID,payload:Uint8Array |
IAgoraRTCClient | stream-type-changed | uid:UID,streamType:RemoteStreamType |
IAgoraRTCClient | token-privilege-did-expire | () |
IAgoraRTCClient | token-privilege-will-expire | () |
IAgoraRTCClient | user-info-updated | uid:UID,msg:|"mute-audio"|"mute-video"|"enable-local-video"|"unmute-audio"|"unmute-video"|"disable-local-video" |
IAgoraRTCClient | user-joined | user:IAgoraRTCRemoteUser |
IAgoraRTCClient | user-left | user:IAgoraRTCRemoteUser,reason:string |
IAgoraRTCClient | user-published | user:IAgoraRTCRemoteUser,mediaType:"audio"|"video"|"datachannel",config?:IDataChannelConfig |
IAgoraRTCClient | user-unpublished | user:IAgoraRTCRemoteUser,mediaType:"audio"|"video"|"datachannel",config?:IDataChannelConfig |
IAgoraRTCClient | volume-indicator | result:{level:number;uid:UID}[] |
IAgoraRTC | audio-context-state-changed | currState:AudioContextState|"interrupted",prevState:AudioContextState|"interrupted"|undefined |
IAgoraRTC | autoplay-failed | () |
IAgoraRTC | camera-changed | deviceInfo:DeviceInfo |
IAgoraRTC | microphone-changed | deviceInfo:DeviceInfo |
IAgoraRTC | playback-device-changed | deviceInfo:DeviceInfo |
IAgoraRTC | security-policy-violation | () |
错误码
| 常量 | 值 |
|---|---|
ADD_CANDIDATE_FAILED | ADD_CANDIDATE_FAILED |
API_INVOKE_TIMEOUT | API_INVOKE_TIMEOUT |
BEAUTY_PROCESSOR_INTERNAL_ERROR | BEAUTY_PROCESSOR_INTERNAL_ERROR |
CAN_NOT_GET_GATEWAY_SERVER | CAN_NOT_GET_GATEWAY_SERVER |
CAN_NOT_GET_PROXY_SERVER | CAN_NOT_GET_PROXY_SERVER |
CAN_NOT_PUBLISH_MULTIPLE_VIDEO_TRACKS | CAN_NOT_PUBLISH_MULTIPLE_VIDEO_TRACKS |
CHROME_PLUGIN_NOT_INSTALL | CHROME_PLUGIN_NOT_INSTALL |
CHROME_PLUGIN_NO_RESPONSE | CHROME_PLUGIN_NO_RESPONSE |
CONSTRAINT_NOT_SATISFIED | CONSTRAINT_NOT_SATISFIED |
CONVERTING_IMAGEDATA_TO_BLOB_FAILED | CONVERTING_IMAGEDATA_TO_BLOB_FAILED |
CONVERTING_VIDEO_FRAME_TO_BLOB_FAILED | CONVERTING_VIDEO_FRAME_TO_BLOB_FAILED |
CREATE_DATACHANNEL_ERROR | CREATE_DATACHANNEL_ERROR |
CREATE_OFFER_FAILED | CREATE_OFFER_FAILED |
CROSS_CHANNEL_FAILED_JOIN_DEST | CROSS_CHANNEL_FAILED_JOIN_DEST |
CROSS_CHANNEL_FAILED_JOIN_SRC | CROSS_CHANNEL_FAILED_JOIN_SEC |
CROSS_CHANNEL_FAILED_PACKET_SENT_TO_DEST | CROSS_CHANNEL_FAILED_PACKET_SENT_TO_DEST |
CROSS_CHANNEL_SERVER_ERROR_RESPONSE | CROSS_CHANNEL_SERVER_ERROR_RESPONSE |
CROSS_CHANNEL_WAIT_STATUS_ERROR | CROSS_CHANNEL_WAIT_STATUS_ERROR |
CUSTOM_REPORT_FREQUENCY_TOO_HIGH | CUSTOM_REPORT_FREQUENCY_TOO_HIGH |
CUSTOM_REPORT_SEND_FAILED | CUSTOM_REPORT_SEND_FAILED |
DATACHANNEL_CONNECTION_TIMEOUT | DATACHANNEL_CONNECTION_TIMEOUT |
DATACHANNEL_FAILED | DATACHANNEL_FAILED |
DECODE_AUDIO_FILE_FAILED | DECODE_AUDIO_FILE_FAILED |
DEVICE_NOT_FOUND | DEVICE_NOT_FOUND |
DISCONNECT_P2P | DISCONNECT_P2P |
ELECTRON_DESKTOP_CAPTURER_GET_SOURCES_ERROR | ELECTRON_DESKTOP_CAPTURER_GET_SOURCES_ERROR |
ELECTRON_IS_NULL | ELECTRON_IS_NULL |
ENUMERATE_DEVICES_FAILED | ENUMERATE_DEVICES_FAILED |
EXCHANGE_SDP_FAILED | EXCHANGE_SDP_FAILED |
EXIST_DISABLED_VIDEO_TRACK | EXIST_DISABLED_VIDEO_TRACK |
EXTERNAL_SIGNAL_ABORT | EXTERNAL_SIGNAL_ABORT |
FETCH_AUDIO_FILE_FAILED | FETCH_AUDIO_FILE_FAILED |
GATEWAY_P2P_LOST | GATEWAY_P2P_LOST |
GET_LOCAL_CAPABILITIES_FAILED | GET_LOCAL_CAPABILITIES_FAILED |
GET_LOCAL_CONNECTION_PARAMS_FAILED | GET_LOCAL_CONNECTION_PARAMS_FAILED |
GET_VIDEO_ELEMENT_VISIBLE_ERROR | GET_VIDEO_ELEMENT_VISIBLE_ERROR |
ICE_FAILED | ICE_FAILED |
IMAGE_MODERATION_UPLOAD_FAILED | IMAGE_MODERATION_UPLOAD_FAILED |
INIT_DATACHANNEL_TIMEOUT | INIT_DATACHANNEL_TIMEOUT |
INVALID_LOCAL_TRACK | INVALID_LOCAL_TRACK |
INVALID_OPERATION | INVALID_OPERATION |
INVALID_PARAMS | INVALID_PARAMS |
INVALID_PLUGIN | INVALID_PLUGIN |
INVALID_REMOTE_USER | INVALID_REMOTE_USER |
INVALID_TRACK | INVALID_TRACK |
INVALID_UINT_UID_FROM_STRING_UID | INVALID_UINT_UID_FROM_STRING_UID |
LIVE_STREAMING_CDN_ERROR | LIVE_STREAMING_CDN_ERROR |
LIVE_STREAMING_INTERNAL_SERVER_ERROR | LIVE_STREAMING_INTERNAL_SERVER_ERROR |
LIVE_STREAMING_INVALID_ARGUMENT | LIVE_STREAMING_INVALID_ARGUMENT |
LIVE_STREAMING_INVALID_RAW_STREAM | LIVE_STREAMING_INVALID_RAW_STREAM |
LIVE_STREAMING_PUBLISH_STREAM_NOT_AUTHORIZED | LIVE_STREAMING_PUBLISH_STREAM_NOT_AUTHORIZED |
LIVE_STREAMING_TASK_CONFLICT | LIVE_STREAMING_TASK_CONFLICT |
LIVE_STREAMING_TRANSCODING_NOT_SUPPORTED | LIVE_STREAMING_TRANSCODING_NOT_SUPPORTED |
LIVE_STREAMING_WARN_FAILED_LOAD_IMAGE | LIVE_STREAMING_WARN_FAILED_LOAD_IMAGE |
LIVE_STREAMING_WARN_FREQUENT_REQUEST | LIVE_STREAMING_WARN_FREQUENT_REQUEST |
LIVE_STREAMING_WARN_STREAM_NUM_REACH_LIMIT | LIVE_STREAMING_WARN_STREAM_NUM_REACH_LIMIT |
LOCAL_AEC_ERROR | LOCAL_AEC_ERROR |
LOW_STREAM_ENCODING_ERROR | LOW_STREAM_ENCODING_ERROR |
MEDIA_OPTION_INVALID | MEDIA_OPTION_INVALID |
METADATA_OUT_OF_RANGE | METADATA_OUT_OF_RANGE |
MULTI_UNILBS_RESPONSE_ERROR | MULTI_UNILBS_RESPONSE_ERROR |
NETWORK_ERROR | NETWORK_ERROR |
NETWORK_RESPONSE_ERROR | NETWORK_RESPONSE_ERROR |
NETWORK_TIMEOUT | NETWORK_TIMEOUT |
NOT_READABLE | NOT_READABLE |
NOT_SUPPORTED | NOT_SUPPORTED |
NO_ICE_CANDIDATE | NO_ICE_CANDIDATE |
OPERATION_ABORTED | OPERATION_ABORTED |
P2P_MESSAGE_FAILED | P2P_MESSAGE_FAILED |
PB_ERROR | PB_ERROR |
PC_CLOSED | PC_CLOSED |
PERMISSION_DENIED | PERMISSION_DENIED |
PROHIBITED_OPERATION | PROHIBITED_OPERATION |
READ_LOCAL_AUDIO_FILE_ERROR | READ_LOCAL_AUDIO_FILE_ERROR |
REMOTE_USER_IS_NOT_PUBLISHED | REMOTE_USER_IS_NOT_PUBLISHED |
SENDER_NOT_FOUND | SENDER_NOT_FOUND |
SENDER_REPLACE_FAILED | SENDER_REPLACE_FAILED |
SET_ANSWER_FAILED | SET_ANSWER_FAILED |
SET_ENCODING_PARAMETER_ERROR | SET_ENCODING_PARAMETER_ERROR |
SHARE_AUDIO_NOT_ALLOWED | SHARE_AUDIO_NOT_ALLOWED |
SUBSCRIBE_FAILED | SUBSCRIBE_FAILED |
TIMEOUT | TIMEOUT |
TOKEN_EXPIRE | TOKEN_EXPIRE |
TRACK_IS_DISABLED | TRACK_IS_DISABLED |
TRACK_STATE_UNREACHABLE | TRACK_STATE_UNREACHABLE |
UID_CONFLICT | UID_CONFLICT |
UNEXPECTED_ERROR | UNEXPECTED_ERROR |
UNEXPECTED_RESPONSE | UNEXPECTED_RESPONSE |
UNSUBSCRIBE_FAILED | UNSUBSCRIBE_FAILED |
UPDATE_TICKET_FAILED | UPDATE_TICKET_FAILED |
VOID_GATEWAY_ADDRESS | VOID_GATEWAY_ADDRESS |
WEBGL_INTERNAL_ERROR | WEBGL_INTERNAL_ERROR |
WEB_SECURITY_RESTRICT | WEB_SECURITY_RESTRICT |
WS_ABORT | WS_ABORT |
WS_DISCONNECT | WS_DISCONNECT |
WS_ERR | WS_ERR |
平台 REST 层用 REST 错误码;SDK 层把连接/鉴权失败映射成声网错误号,映射表见 各端 SDK 错误标识。