nimeet Docs
中文

Web SDK Guide

Web SDK install, join, publish/subscribe, screen sharing and browser support.

Interface sections generated from the public SDK definitions

Version v1.1.0 · Updated 2026-10-26 · revision 1

The browser SDK exposes the same public API as Agora Web SDK 4.24.9: 142 methods, 153 overloads, 47 events and 96 error identifiers. The method chain below covers the full path from creating a client and joining a channel to publishing, subscribing and leaving, and can be reused directly in your application.

Prerequisites

  • Node.js ≥ 22 (for builds and tests) and pnpm 10; run pnpm install.
  • Browsers: Chrome 65+, Edge, Firefox 69+, Safari 11+ (screen sharing needs Safari 13+).
  • Capturing audio and video requires a secure context: https:// or http://localhost; over a plain http:// LAN IP, getUserMedia fails outright.
  • You have obtained an appId as described in Enable services and get credentials, plus a backend that can issue tokens.

Install

bash
pnpm add @nimeet/rtc-web

When developing inside the codebase, use the workspace package: pnpm --filter @nimeet/rtc-web build. The version matches the reference version (the apiVersion in the SDK interface definition matches the interface definition section).

Get a token

The browser cannot hold customerSecret. The correct chain is your backend calling the control plane:

javascript
// Your backend (Node example):
// POST $NIMEET_API_BASE/v1/tokens  { channelName, uid, role, ttlSeconds }
// The frontend only receives { appId, rtcToken, rtcUid }
const response = await fetch('/api/rtc-token?channel=weekly-sync&uid=10001');
const { appId, rtcToken, rtcUid } = await response.json();

For token semantics (privilege bits, the exp/pexp split, wildcards) see AccessToken2 semantics.

Join a channel

javascript
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);

The page needs two containers: <div id="local-player"></div> and <div id="remote-player"></div>. The media edge address is resolved in the order surl in the token payload → window.__NIMEET_SFU_URL__ → same-origin /rtc.

Publish and subscribe

javascript
const [microphoneTrack, cameraTrack] = await AgoraRTC.createMicrophoneAndCameraTracks();
cameraTrack.play('local-player');
await client.publish([microphoneTrack, cameraTrack]);

// Manual subscription (auto-subscribe is the default; once disabled you must subscribe yourself)
await client.unsubscribe(remoteUser, 'video');
cameraTrack.setEnabled(false); // mute the video but keep publishing

Tracks are objects: what you publish is an ILocalTrack instance, not a "resource id". To stop publishing call client.unpublish(track); to release the capture call track.close().

Events

EventWhen it firesTypical handling
user-joined / user-leftA remote user enters or leavesAllocate/release the video container
user-published / user-unpublishedA remote user publishes or unpublishessubscribe and then play, or stop
connection-state-changeDISCONNECTED / CONNECTING / CONNECTED / RECONNECTINGDo not destroy tracks while RECONNECTING
token-privilege-will-expire30 seconds before the privilege expiresRenew and call renewToken
network-qualityPeriodic uplink/downlink quality reportsWarn about a weak network, or report to Crystal Ball
stream-messageData channel messagesWhiteboard or signalling payloads
exceptionAn SDK-internal exceptionCollect logs and route by code

Error handling

Every failure is an AgoraRTCError: { code, message, data? }, where code is an Agora 4.x string identifier (INVALID_PARAMS, PERMISSION_DENIED, TOKEN_EXPIRE…); the full table of 96 entries is in this platform's API reference. Platform REST error codes also carry sdkCode (a negative error number); for the mapping see SDK error identifiers per platform.

javascript
try {
  await client.publish([microphoneTrack, cameraTrack]);
} catch (error) {
  if (error.code === 'PERMISSION_DENIED') {
    // The user denied the camera/microphone permission
  } else if (error.code === 'NOT_SUPPORTED') {
    // The capability is not implemented on the current media stack (for example E2EE); do not ignore it silently
  }
}

Troubleshooting

SymptomCauseHandling
getUserMedia throws immediatelyNot a secure context, or permission deniedUse https:// or localhost; check the browser's site permissions
Joining reports INVALID_TOKENThe token does not match the appId/channel, or has expiredSelf-check with GET /v1/tokens/verify?token=&channelName=
Audio works but there is no videoThe remote video track was never subscribed, or the container id is wrongCheck whether the user-published handler calls play('remote-player')
Safari cannot captureCapture needs a user gestureTrigger capture from a button click handler
Stuck in RECONNECTINGThe media edge is unreachableCheck the SFU address resolution chain and the reverse proxy's WebSocket upgrade
Bundle size is too largeNo tree-shakingFollow Agora's on-demand import approach: import { AgoraRTC } from '@nimeet/rtc-web'

Next steps

Platform information

Itemvalue
PlatformWeb(browser)
LanguageTypeScript
SDK package@nimeet/rtc-web
Interface version4.24.9
Compatible Agora APIAgora javascript API

The public interfaces are aligned with the Agora javascript API; installation and runtime requirements are listed under Prerequisites above.

Availability

This platform's SDK: Generally available. The examples use the public API; runtime capability follows the published SDK package.

join-channel flow

The method names, parameter types and return values match the API reference for the same platform. Where a method has several overloads, the most common one is shown here; see the API reference for all overloads.

StepInterfaceMethodOverload count
Join a channelIAgoraRTCClientjoin1
Leave a channelIAgoraRTCClientleave1
publish the local trackIAgoraRTCClientpublish3
Renew the TokenIAgoraRTCClientrenewToken1
switch rolesIAgoraRTCClientsetClientRole1
subscribe to remote mediaIAgoraRTCClientsubscribe4
Create instance / initializeIAgoraRTCcreateClient1
typescript
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

Events and callbacks

Interfaceevent namelistener parameters
ILocalTracktrack-ended()
ILocalTracktrack-updatedtrack:MediaStreamTrack
ILocalTracktransceiver-updatedtransceiver:RTCRtpTransceiver,type?:StreamType
ILocalVideoTracktrack-ended()
ILocalVideoTracktrack-updatedtrack:MediaStreamTrack
ILocalVideoTracktransceiver-updatedtransceiver:RTCRtpTransceiver,type?:StreamType
ILocalVideoTrackvideo-element-visible-statusdata?:CheckVideoVisibleResult
IRemoteTrackfirst-frame-decoded()
IRemoteTracktrack-updatedtrack:MediaStreamTrack
IRemoteTracktransceiver-updatedtransceiver:RTCRtpTransceiver
IRemoteVideoTrackfirst-frame-decoded()
IRemoteVideoTracksei-receivedsei:Uint8Array,rtpTimestamp?:number
IRemoteVideoTracktrack-updatedtrack:MediaStreamTrack
IRemoteVideoTracktransceiver-updatedtransceiver:RTCRtpTransceiver
IRemoteVideoTrackvideo-element-visible-statusdata?:CheckVideoVisibleResult
IRemoteVideoTrackvideo-state-changedvideoState:VideoState
IAgoraRTCClientchannel-media-relay-eventevent:ChannelMediaRelayEvent
IAgoraRTCClientchannel-media-relay-statestate:ChannelMediaRelayState,code:ChannelMediaRelayError
IAgoraRTCClientconnection-state-changecurState:ConnectionState,revState:ConnectionState,reason?:ConnectionDisconnectedReason
IAgoraRTCClientcrypt-error()
IAgoraRTCClientexceptionevent:{code:number;msg:string;uid:UID}
IAgoraRTCClientis-using-cloud-proxyisUsingProxy:boolean
IAgoraRTCClientjoin-fallback-to-proxyproxyServer:string
IAgoraRTCClientlive-streaming-errorurl:string,err:AgoraRTCError
IAgoraRTCClientlive-streaming-warningurl:string,warning:AgoraRTCError
IAgoraRTCClientmedia-reconnect-enduid:UID
IAgoraRTCClientmedia-reconnect-startuid:UID
IAgoraRTCClientnetwork-qualitystats:NetworkQuality
IAgoraRTCClientpeerconnection-state-changecurState:RTCPeerConnectionState,revState:RTCPeerConnectionState
IAgoraRTCClientpublished-user-listusers:IAgoraRTCRemoteUser[]
IAgoraRTCClientstream-fallbackuid:UID,isFallbackOrRecover:"fallback"|"recover"
IAgoraRTCClientstream-messageuid:UID,payload:Uint8Array
IAgoraRTCClientstream-type-changeduid:UID,streamType:RemoteStreamType
IAgoraRTCClienttoken-privilege-did-expire()
IAgoraRTCClienttoken-privilege-will-expire()
IAgoraRTCClientuser-info-updateduid:UID,msg:|"mute-audio"|"mute-video"|"enable-local-video"|"unmute-audio"|"unmute-video"|"disable-local-video"
IAgoraRTCClientuser-joineduser:IAgoraRTCRemoteUser
IAgoraRTCClientuser-leftuser:IAgoraRTCRemoteUser,reason:string
IAgoraRTCClientuser-publisheduser:IAgoraRTCRemoteUser,mediaType:"audio"|"video"|"datachannel",config?:IDataChannelConfig
IAgoraRTCClientuser-unpublisheduser:IAgoraRTCRemoteUser,mediaType:"audio"|"video"|"datachannel",config?:IDataChannelConfig
IAgoraRTCClientvolume-indicatorresult:{level:number;uid:UID}[]
IAgoraRTCaudio-context-state-changedcurrState:AudioContextState|"interrupted",prevState:AudioContextState|"interrupted"|undefined
IAgoraRTCautoplay-failed()
IAgoraRTCcamera-changeddeviceInfo:DeviceInfo
IAgoraRTCmicrophone-changeddeviceInfo:DeviceInfo
IAgoraRTCplayback-device-changeddeviceInfo:DeviceInfo
IAgoraRTCsecurity-policy-violation()

Error codes

Constantvalue
ADD_CANDIDATE_FAILEDADD_CANDIDATE_FAILED
API_INVOKE_TIMEOUTAPI_INVOKE_TIMEOUT
BEAUTY_PROCESSOR_INTERNAL_ERRORBEAUTY_PROCESSOR_INTERNAL_ERROR
CAN_NOT_GET_GATEWAY_SERVERCAN_NOT_GET_GATEWAY_SERVER
CAN_NOT_GET_PROXY_SERVERCAN_NOT_GET_PROXY_SERVER
CAN_NOT_PUBLISH_MULTIPLE_VIDEO_TRACKSCAN_NOT_PUBLISH_MULTIPLE_VIDEO_TRACKS
CHROME_PLUGIN_NOT_INSTALLCHROME_PLUGIN_NOT_INSTALL
CHROME_PLUGIN_NO_RESPONSECHROME_PLUGIN_NO_RESPONSE
CONSTRAINT_NOT_SATISFIEDCONSTRAINT_NOT_SATISFIED
CONVERTING_IMAGEDATA_TO_BLOB_FAILEDCONVERTING_IMAGEDATA_TO_BLOB_FAILED
CONVERTING_VIDEO_FRAME_TO_BLOB_FAILEDCONVERTING_VIDEO_FRAME_TO_BLOB_FAILED
CREATE_DATACHANNEL_ERRORCREATE_DATACHANNEL_ERROR
CREATE_OFFER_FAILEDCREATE_OFFER_FAILED
CROSS_CHANNEL_FAILED_JOIN_DESTCROSS_CHANNEL_FAILED_JOIN_DEST
CROSS_CHANNEL_FAILED_JOIN_SRCCROSS_CHANNEL_FAILED_JOIN_SEC
CROSS_CHANNEL_FAILED_PACKET_SENT_TO_DESTCROSS_CHANNEL_FAILED_PACKET_SENT_TO_DEST
CROSS_CHANNEL_SERVER_ERROR_RESPONSECROSS_CHANNEL_SERVER_ERROR_RESPONSE
CROSS_CHANNEL_WAIT_STATUS_ERRORCROSS_CHANNEL_WAIT_STATUS_ERROR
CUSTOM_REPORT_FREQUENCY_TOO_HIGHCUSTOM_REPORT_FREQUENCY_TOO_HIGH
CUSTOM_REPORT_SEND_FAILEDCUSTOM_REPORT_SEND_FAILED
DATACHANNEL_CONNECTION_TIMEOUTDATACHANNEL_CONNECTION_TIMEOUT
DATACHANNEL_FAILEDDATACHANNEL_FAILED
DECODE_AUDIO_FILE_FAILEDDECODE_AUDIO_FILE_FAILED
DEVICE_NOT_FOUNDDEVICE_NOT_FOUND
DISCONNECT_P2PDISCONNECT_P2P
ELECTRON_DESKTOP_CAPTURER_GET_SOURCES_ERRORELECTRON_DESKTOP_CAPTURER_GET_SOURCES_ERROR
ELECTRON_IS_NULLELECTRON_IS_NULL
ENUMERATE_DEVICES_FAILEDENUMERATE_DEVICES_FAILED
EXCHANGE_SDP_FAILEDEXCHANGE_SDP_FAILED
EXIST_DISABLED_VIDEO_TRACKEXIST_DISABLED_VIDEO_TRACK
EXTERNAL_SIGNAL_ABORTEXTERNAL_SIGNAL_ABORT
FETCH_AUDIO_FILE_FAILEDFETCH_AUDIO_FILE_FAILED
GATEWAY_P2P_LOSTGATEWAY_P2P_LOST
GET_LOCAL_CAPABILITIES_FAILEDGET_LOCAL_CAPABILITIES_FAILED
GET_LOCAL_CONNECTION_PARAMS_FAILEDGET_LOCAL_CONNECTION_PARAMS_FAILED
GET_VIDEO_ELEMENT_VISIBLE_ERRORGET_VIDEO_ELEMENT_VISIBLE_ERROR
ICE_FAILEDICE_FAILED
IMAGE_MODERATION_UPLOAD_FAILEDIMAGE_MODERATION_UPLOAD_FAILED
INIT_DATACHANNEL_TIMEOUTINIT_DATACHANNEL_TIMEOUT
INVALID_LOCAL_TRACKINVALID_LOCAL_TRACK
INVALID_OPERATIONINVALID_OPERATION
INVALID_PARAMSINVALID_PARAMS
INVALID_PLUGININVALID_PLUGIN
INVALID_REMOTE_USERINVALID_REMOTE_USER
INVALID_TRACKINVALID_TRACK
INVALID_UINT_UID_FROM_STRING_UIDINVALID_UINT_UID_FROM_STRING_UID
LIVE_STREAMING_CDN_ERRORLIVE_STREAMING_CDN_ERROR
LIVE_STREAMING_INTERNAL_SERVER_ERRORLIVE_STREAMING_INTERNAL_SERVER_ERROR
LIVE_STREAMING_INVALID_ARGUMENTLIVE_STREAMING_INVALID_ARGUMENT
LIVE_STREAMING_INVALID_RAW_STREAMLIVE_STREAMING_INVALID_RAW_STREAM
LIVE_STREAMING_PUBLISH_STREAM_NOT_AUTHORIZEDLIVE_STREAMING_PUBLISH_STREAM_NOT_AUTHORIZED
LIVE_STREAMING_TASK_CONFLICTLIVE_STREAMING_TASK_CONFLICT
LIVE_STREAMING_TRANSCODING_NOT_SUPPORTEDLIVE_STREAMING_TRANSCODING_NOT_SUPPORTED
LIVE_STREAMING_WARN_FAILED_LOAD_IMAGELIVE_STREAMING_WARN_FAILED_LOAD_IMAGE
LIVE_STREAMING_WARN_FREQUENT_REQUESTLIVE_STREAMING_WARN_FREQUENT_REQUEST
LIVE_STREAMING_WARN_STREAM_NUM_REACH_LIMITLIVE_STREAMING_WARN_STREAM_NUM_REACH_LIMIT
LOCAL_AEC_ERRORLOCAL_AEC_ERROR
LOW_STREAM_ENCODING_ERRORLOW_STREAM_ENCODING_ERROR
MEDIA_OPTION_INVALIDMEDIA_OPTION_INVALID
METADATA_OUT_OF_RANGEMETADATA_OUT_OF_RANGE
MULTI_UNILBS_RESPONSE_ERRORMULTI_UNILBS_RESPONSE_ERROR
NETWORK_ERRORNETWORK_ERROR
NETWORK_RESPONSE_ERRORNETWORK_RESPONSE_ERROR
NETWORK_TIMEOUTNETWORK_TIMEOUT
NOT_READABLENOT_READABLE
NOT_SUPPORTEDNOT_SUPPORTED
NO_ICE_CANDIDATENO_ICE_CANDIDATE
OPERATION_ABORTEDOPERATION_ABORTED
P2P_MESSAGE_FAILEDP2P_MESSAGE_FAILED
PB_ERRORPB_ERROR
PC_CLOSEDPC_CLOSED
PERMISSION_DENIEDPERMISSION_DENIED
PROHIBITED_OPERATIONPROHIBITED_OPERATION
READ_LOCAL_AUDIO_FILE_ERRORREAD_LOCAL_AUDIO_FILE_ERROR
REMOTE_USER_IS_NOT_PUBLISHEDREMOTE_USER_IS_NOT_PUBLISHED
SENDER_NOT_FOUNDSENDER_NOT_FOUND
SENDER_REPLACE_FAILEDSENDER_REPLACE_FAILED
SET_ANSWER_FAILEDSET_ANSWER_FAILED
SET_ENCODING_PARAMETER_ERRORSET_ENCODING_PARAMETER_ERROR
SHARE_AUDIO_NOT_ALLOWEDSHARE_AUDIO_NOT_ALLOWED
SUBSCRIBE_FAILEDSUBSCRIBE_FAILED
TIMEOUTTIMEOUT
TOKEN_EXPIRETOKEN_EXPIRE
TRACK_IS_DISABLEDTRACK_IS_DISABLED
TRACK_STATE_UNREACHABLETRACK_STATE_UNREACHABLE
UID_CONFLICTUID_CONFLICT
UNEXPECTED_ERRORUNEXPECTED_ERROR
UNEXPECTED_RESPONSEUNEXPECTED_RESPONSE
UNSUBSCRIBE_FAILEDUNSUBSCRIBE_FAILED
UPDATE_TICKET_FAILEDUPDATE_TICKET_FAILED
VOID_GATEWAY_ADDRESSVOID_GATEWAY_ADDRESS
WEBGL_INTERNAL_ERRORWEBGL_INTERNAL_ERROR
WEB_SECURITY_RESTRICTWEB_SECURITY_RESTRICT
WS_ABORTWS_ABORT
WS_DISCONNECTWS_DISCONNECT
WS_ERRWS_ERR

the platform REST layer uses REST error codes;the SDK layer maps connection/authentication failures to Agora error numbers, for the mapping table see SDK error identifiers per platform。

Generated by the documentation build pipeline · 2026-10-31 01:24:02 UTC · Documentation center v1.1.0.

Content follows the public API definitions.

ni-meet is a self-hosted RTC PaaS with a LiveKit-based media plane (Apache-2.0) and an Agora-compatible API.