UNPKG

@vdoninja/sdk

Version:

AI-friendly P2P communication SDK for audio, video, and data. Includes WHIP/WHEP clients for publishing to Twitch, Meshcast, Cloudflare Stream

425 lines (340 loc) 19.8 kB
# VDO.Ninja SDK — API Reference This page lists the primary methods, helpers, aliases, and events exposed by the SDK. It complements the short examples in README. Tip: For custom app protocols, avoid using reserved types 'subscribe' | 'unsubscribe' | 'channelMessage' on the pipe payload — those are used by the SDK pub/sub system. ## Constructor ```js const vdo = new VDONinjaSDK(options) ``` - host: WebSocket URL (default: `wss://wss.vdo.ninja`) - room: Initial room to join (optional) - password: Room password or false to disable encryption (default: autogenerated key) - label: Optional human-readable label for your stream/view - debug: Enable SDK logs (boolean) - turnServers: null=auto-fetch, false=disable, or custom ICE servers - forceTURN: Force relay mode (boolean) - stunServers, configuration, salt, plus advanced info flags (see README for details) - autoRecover: Recover failed peer directions automatically (default: true) - autoRelay: Temporarily escalate failed direct paths to TURN (default: true) - disconnectGracePeriod: Grace period for temporary ICE disconnects (default: 5000 ms) - connectionTimeout: Initial peer connection timeout (default: 20000 ms) - recoveryTimeout: Wait between bounded recovery phases (default: 12000 ms) - relayRestoreDelay: Restore direct-first ICE policy after recovery (default: 45000 ms) ## Connection - connect(): Promise<void> - disconnect(): Promise<void> - Resolves once teardown genuinely completes: bye messages flushed, peers closed, timers cleared, socket shut. Exiting the process before it resolves can crash the native WebRTC module mid-teardown. - Safe to call more than once; repeat calls return the same promise and do not tear down twice. - The returned promise is new in v1.5. Callers that ignore it behave exactly as before. - `disconnected` is **not** a completion signal — it also fires when the socket closes, partway through. Await the promise, or listen for `teardownComplete`. - joinRoom({ room, password }): Promise<void> - leaveRoom(): void - autoConnect(roomOrOptions, filter?): Promise<{ stop: Function, streamID: string }> - Shorthand to connect, join, announce, and view peers in a mesh. - Options: { room, mode: 'half' | 'full', view: { audio, video }, label, password, streamID?, filter } ## Publishing (Sender) - publish(stream, { streamID, room?, label?, password?, media? }): Promise<string> - media: { video?: { codec?, maxBitrate?, resolution?, frameRate? }, audio?: { codec?, maxBitrate? } } - announce({ streamID?, room?, label?, password? }): Promise<string> - Data-only publishing; generates a streamID if not provided. - stopPublishing(): void - updatePublisherMedia({ media?, videoBitrate?, videoCodec?, clear? }): Promise<Object|null> - Re-applies bitrate/codec/resolution preferences to the active publisher; use `clear:true` to reset. ## Viewing (Receiver) - view(streamID, { audio=true, video=true, label?, downloads=true, allowresources=false }): Promise<RTCPeerConnection> - downloads: advertise willingness to receive file offers. A VDO.Ninja publisher only sends its file list to a viewer that asked for it, so turning this off means no `fileList` event arrives at connect time. - allowresources: advertise willingness to receive the `resources` channel. Off by default, matching VDO.Ninja, where it requires the `&resources` URL flag. - stopViewing(streamID): void ## Quick Helpers - quickPublish({ stream, room?, streamID?, label?, password? }): Promise<string> - quickView({ streamID, room?, audio?, video?, label?, password?, dataOnly? }): Promise<RTCPeerConnection> - dataOnly: true maps to { audio:false, video:false } - quickSubscribe({ streamID, room?, ... } = {}): Promise<RTCPeerConnection> - Convenience wrapper around quickView that defaults to dataOnly unless explicitly overridden. ## Data Communication - sendData(data, target?): boolean - target: undefined → all connected peers; 'uuid' → a specific peer; { uuid?, type?: 'viewer'|'publisher', streamID?, preference?: 'any'|'viewer'|'publisher'|'all', allowFallback? } - preference controls data-channel routing. 'any' (default) tries publisher first, then viewer; an explicit role uses only that role; 'all' may duplicate messages. - allowFallback controls the separate WebSocket signaling fallback. It defaults to false; set it to true to opt in. - sendPing(uuid?): boolean - request(requestType, data, targetUUID, timeout=5000): Promise<any> - respond(requestId, data, targetUUID): boolean - onRequest(requestType, handler): void ## Binary and Additional Channels Turns the SDK from a messaging transport into a bulk transport: raw bytes, a second channel so bulk traffic stops head-of-line blocking control messages, partial reliability, and a real backpressure signal. Everything here lives in the reserved `x-` namespace, which VDO.Ninja ignores by contract. A reserved channel is therefore safe to open toward any peer — verified with 4MB flooded at a live VDO.Ninja tab, which saw zero errors and kept its control channel. - sendBinary(data, uuid, { ordered?, maxRetransmits?, maxPacketLifeTime?, waitForDrain?, timeout? }): Promise&lt;boolean&gt; - Bytes go out untouched — no JSON, no base64. - **Never uses the control channel.** VDO.Ninja renders any binary payload there as a WebP image, so raw bytes would visibly corrupt a viewer. `sendBinary` uses a dedicated `x-bin` lane instead. A VDO.Ninja peer ignores that lane, so nothing breaks; it simply will not receive the bytes, having no generic binary sink. - `waitForDrain` defaults true, applying backpressure before each send. - openChannel(uuid, label, { ordered?, maxRetransmits?, maxPacketLifeTime?, protocol?, timeout? }): Promise&lt;RTCDataChannel&gt; - The label is forced into the `x-` namespace; `'bulk'` becomes `'x-bulk'`. - Resolves once the channel is open. Idempotent — reopening returns the existing channel. - `ordered: false` plus `maxRetransmits` or `maxPacketLifeTime` gives partially-reliable delivery, which suits chunked transfer that already indexes and hashes its own chunks. - `maxRetransmits` and `maxPacketLifeTime` are mutually exclusive; supplying both throws. - getChannel(uuid, label): RTCDataChannel | null - getBufferedAmount(uuid, label?): number | null - Omit `label` for the control channel. Null if the peer or channel is unknown. - getMaxMessageSize(uuid): number | null - The negotiated SCTP limit. Null when the transport has not reported one — see the implementation note below. On null, 65536 is the conventional safe assumption; VDO.Ninja's own file transfer uses 16384. Events: - binaryReceived: { uuid, streamID, bytes: Uint8Array, data } - channelOpen: { uuid, streamID, label, channel } — a peer opened a reserved channel other than the SDK's own `x-bin` lane; the raw channel is handed over - bufferedAmountLow: { uuid, streamID, label, bufferedAmount } - Emitted after an observed queue above the SDK's 256 KiB low-water mark drains to or below it. A positive value already below the mark does not imply a pending event. ```js // Bulk on its own channel, unreliable and unordered, with backpressure const bulk = await vdo.openChannel(uuid, 'bulk', { ordered: false, maxRetransmits: 0 }); for (const chunk of chunks) { while (vdo.getBufferedAmount(uuid, 'bulk') > 1_000_000) { await new Promise(r => vdo.once('bufferedAmountLow', r)); } bulk.send(chunk); } ``` ### Implementation note: backpressure needs a transport that reports it Some `@roamhq/wrtc` builds report `bufferedAmount: 0` no matter how much is queued — 2.4MB in Windows testing. Other builds report queued bytes but omit the native `bufferedamountlow` event; the SDK polls as a fallback for those builds. If a build always reports zero, `getBufferedAmount` remains zero, `bufferedAmountLow` cannot observe a high-to-low transition, and `waitForDrain` is a no-op. Browsers report it correctly. This is a limitation of the WebRTC implementation, not the SDK. If you need flow control in Node today, keep an application-level cap on outstanding sends rather than relying on the drain signal. ## File Transfer Implements VDO.Ninja's native file transfer, so an SDK peer and a VDO.Ninja browser tab can exchange files in either direction. Files move over their own data channel, never the control channel. See `docs/compatibility.md` for the wire format. Hosting: - hostFile(source, { name?, id?, restricted=false }): { id, name, size } - source: Blob, File, ArrayBuffer, or any typed array. `name` is required unless the source is a File. - restricted: a peer UUID to offer the file to only that peer; `false` offers it to all. - Advertises the new file immediately to eligible publisher-side peers that sent `downloads: true`. - unhostFile(id): boolean - Stops serving the file and cancels transfers in flight. Note that VDO.Ninja's protocol has no un-advertise message, so a peer that already saw the offer keeps displaying it; requesting it afterwards is refused. - getHostedFiles(): Array<{ id, name, size, restricted }> Receiving: - requestFile(uuid, fileId, { stream=false, timeout=30000 }): Promise<Result> - Result: `{ id, name, size, uuid, streamID, bytes: Uint8Array, blob?: Blob }` - stream: true emits `fileChunk` events instead of buffering the whole file in memory; `bytes` is then omitted. - Rejects if the peer never starts the transfer, if the channel closes early, or if the delivered byte count does not match the announced size. Events: - fileList: { uuid, streamID, files: [{ id, name, size }] } — a peer advertised files - fileTransferStart: { uuid, id, name, size, direction, requested } - fileTransferProgress: { uuid, id, name, direction, bytes, size, progress } - fileChunk: { uuid, id, name, chunk: Uint8Array, bytes, size } — streaming mode only - fileTransferComplete: { uuid, id, name, size, direction } - fileTransferCancelled: { uuid, id, name, direction } - fileTransferError: { uuid, id, name, direction, error } `direction` is `'inbound'` or `'outbound'` on every transfer event. ```js // Host a file and let a VDO.Ninja viewer download it from its chat feed const vdo = new VDONinjaSDK(); await vdo.connect(); await vdo.joinRoom({ room: 'myroom' }); await vdo.announce({ streamID: 'mystream' }); const offered = vdo.hostFile(bytes, { name: 'report.pdf' }); // Or download what a peer is offering vdo.addEventListener('fileList', async (e) => { const file = e.detail.files[0]; const { bytes } = await vdo.requestFile(e.detail.uuid, file.id); }); ``` ## Resources VDO.Ninja's `resources` channel carries images keyed by meta template name. The receiver turns each into an object URL and stores it under `meta[templateName].value`. - sendResource(uuid, metadata, data): Promise<void> - metadata: must include `templateName`; `type` sets the MIME type (default image/png); `size` is filled in for you. - data: ArrayBuffer or typed array. - Throws unless the peer advertised `allowresources`, which VDO.Ninja viewers do via the `&resources` URL flag and SDK viewers via `view(id, { allowresources: true })`. - The publisher's `meta` must be an **object** keyed by template name for a VDO.Ninja receiver to store anything; it rejects a string. Event: - resourceReceived: { uuid, streamID, metadata, bytes: Uint8Array } ## Pub/Sub - subscribe(channels: string | string[]): void - unsubscribe(channels: string | string[]): void - getSubscriptions(): string[] - publishToChannel(channel: string, data: any, target = 'all'): boolean - getPeerSubscriptions(uuid: string): string[] Events: - channelMessage: { channel, data, timestamp, uuid } — only emitted if locally subscribed - peerSubscribed: { uuid, channels, allChannels } - peerUnsubscribed: { uuid, channels, allChannels } ## Utilities - getStats(uuid?): Promise<RTCStatsReport | any> - getPeerQuality(uuid): Promise<PeerQuality | null> - Digested per-peer link quality, so a peer can be ranked as soon as ICE settles instead of after the application has measured RTT itself. - `{ rttMs, lossRate, candidatePairType, relayed, availableOutgoingBitrate, bytesSent, bytesReceived }` - `rttMs` is milliseconds (the underlying stat is in seconds). `candidatePairType` looks like `"host/srflx"` or `"relay/host"`. - `lossRate` is `null` on a data-only peer rather than a misleading zero — data channels carry no RTP, so there is nothing to measure loss against. - Returns `null` for an unknown peer or when no statistics are available. - on/off/once(eventName, handler): chaining shorthands for add/removeEventListener ## TypeScript Type definitions ship with the package (`vdoninja-sdk.d.ts`); no `@types` install needed. ```ts import VDONinja, { PeerQuality, FileTransferResult } from '@vdoninja/sdk'; ``` `on`/`off`/`once` are typed against the event map, so `e.detail` is inferred per event name. `npm run test:types` typechecks a consumer against the shipped declarations under `--strict`, so the definitions cannot silently drift from the implementation. ## Aliases (Common Names) - Viewing: play(), watch(), startViewing() → view() - Publishing: stream(), broadcast(), startPublishing(), share() → publish() - Quick: quickStream(), quickBroadcast(), quickShare() → quickPublish() - Stop viewing: stop(), stopPlaying(), stopWatching() → stopViewing() - Stop publishing: stopStreaming(), stopBroadcasting(), stopSharing(), unpublish() → stopPublishing() - Connection: join(), enterRoom(), enter() → joinRoom(); leave(), exitRoom() → leaveRoom() Note: The viewing alias `unsubscribe(streamID)` that conflicted with pub/sub has been removed. ## Events (Selected) Connection & Room - connected - disconnected { intentional, reason, willReconnect, phase } - Fires twice on a deliberate disconnect: once with `phase: 'socket'` when the socket closes, once with `phase: 'teardown'` when cleanup finishes. `intentional` distinguishes a local `disconnect()` from a dropped connection, so callers no longer have to keep their own flag to avoid announcing a reconnect that will not happen. - teardownComplete { reason } — emitted exactly once, only when cleanup genuinely finishes - reconnecting, reconnected, reconnectFailed - connectionRecovering, connectionRecovered, connectionFailed, relayEscalated, relayRestored - iframe-friendly aliases: hss-connection, room-peer-listing, push-connection, view-connection - roomJoined { room }, roomLeft { room } - listing { list, raw }, peerListing (raw VDO.Ninja listings) Peer & Channel - peerConnected { uuid, connection } - peerDisconnected { uuid } - dataChannelOpen { uuid, type, streamID }, dataChannelClose { uuid, type, streamID } - peerInfo { uuid, streamID, info } - peerLatency { uuid, latency, streamID } Data - dataReceived { data, uuid, streamID?, fallback? } - data (legacy, original WS/DC format) - Typo alias also emitted: dataRecieved File Transfer & Resources (see the sections above for payloads) - fileList, fileTransferStart, fileTransferProgress, fileChunk - fileTransferComplete, fileTransferCancelled, fileTransferError - resourceReceived - channelOpen { uuid, streamID, label, channel } — a peer opened a reserved `x-*` channel. The raw `RTCDataChannel` is handed over; the application owns whatever protocol runs on it. VDO.Ninja ignores these labels by contract, so they are safe to open toward any peer. - unsupportedChannel { uuid, streamID, label } — a peer opened an auxiliary channel this SDK build does not speak (currently `chunked`); it is accepted and ignored rather than mis-routed Media - track { track, streams?, uuid, streamID } - trackAdded, trackRemoved, trackReplaced State & Errors - publishing { streamID, hashedStreamID } - publishingStopped - viewingStopped { streamID } - connectionFailed { reason, ... } - iceRestart { uuid, streamID, reason } - approved, rejected, bye, hangup, transferred, alert { message }, error { error, details? } ## Compatibility Notes - Reserved pipe types: 'subscribe' | 'unsubscribe' | 'channelMessage' are used by the SDK pub/sub system and do not emit dataReceived; use the pub/sub helpers and events. - Viewer preferences (audio/video) are sent viewer → publisher via the viewer data channel, not the reverse. - Legacy support: quickView and autoConnect can infer data-only; you can also pass { dataOnly:true }. - WebSocket fallback: sendData can deliver via signaling when allowFallback is true (default false); dataReceived includes { fallback:true }. --- ## WHIP/WHEP Clients Standalone clients for standard WebRTC-HTTP streaming protocols. These work independently of the VDO.Ninja P2P system. ### WHIPClient (whip-client.js) Publish media streams to WHIP-compatible endpoints (Twitch, Meshcast, Cloudflare, etc.) ```js const client = new WHIPClient(endpoint, options) ``` Options: - endpoint: WHIP endpoint URL (required) - authToken: Bearer token for authentication - videoCodec: Preferred codec ('h264', 'vp8', 'vp9', 'av1') - videoBitrate: Target video bitrate in kbps - audioBitrate: Target audio bitrate in kbps - trickleIce: Enable trickle ICE (default: true) - iceServers: Custom ICE servers array - headers: Additional HTTP headers - debug: Enable debug logging Methods: - publish(stream): Promise<void> — Publish a MediaStream - replaceTrack(oldTrack, newTrack): Promise<void> — Replace a track mid-session - stop(): Promise<void> — Stop publishing and cleanup - getStats(): Promise<RTCStatsReport> — Get connection statistics - restartIce(): Promise<void> — Restart ICE connection Events: connecting, connected, icestate, connectionstate, error, disconnected, stopped ### WHEPClient (whep-client.js) Consume media streams from WHEP-compatible endpoints. ```js const client = new WHEPClient(endpoint, options) ``` Options: - endpoint: WHEP endpoint URL (required) - authToken: Bearer token for authentication - audio: Request audio track (default: true) - video: Request video track (default: true) - trickleIce: Enable trickle ICE (default: true) - iceServers: Custom ICE servers array - headers: Additional HTTP headers - debug: Enable debug logging Methods: - view(): Promise<MediaStream> — Start viewing - getStream(): MediaStream | null — Get the received MediaStream - muteAudio(muted): void — Mute/unmute audio locally - muteVideo(muted): void — Mute/unmute video locally - stop(): Promise<void> — Stop viewing and cleanup - getStats(): Promise<RTCStatsReport> — Get connection statistics - restartIce(): Promise<void> — Restart ICE connection Events: connecting, connected, track, icestate, connectionstate, error, disconnected, stopped ### Supported WHIP/WHEP Services | Service | WHIP URL | WHEP URL | |---------|----------|----------| | Meshcast.io | `https://cae1.meshcast.io/whip/{streamId}` | `https://cae1.meshcast.io/whep/{streamId}` | | Twitch | `https://g.webrtc.live-video.net:4443/v2/offer` | N/A | | Cloudflare Stream | Your Stream endpoint | Your Stream endpoint | | Dolby.io | Your Dolby endpoint | Your Dolby endpoint | ### WHIP/WHEP Example ```js // Publish to Meshcast const whip = new WHIPClient('https://cae1.meshcast.io/whip/mystream', { videoCodec: 'h264', videoBitrate: 2500 }); const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); await whip.publish(stream); // View at: https://meshcast.io/mystream // Watch from Meshcast const whep = new WHEPClient('https://cae1.meshcast.io/whep/mystream'); whep.addEventListener('track', (e) => { document.getElementById('video').srcObject = e.detail.streams[0]; }); await whep.view(); ``` --- See README for end-to-end examples and the demos folder for runnable samples.