npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-nitro-ndi

v0.1.0

Published

NDI video and audio for React Native — discover sources, send frames, and receive from the network.

Readme

react-native-nitro-ndi

NDI (Network Device Interface) for React Native, powered by Nitro Modules.

Discover NDI sources, send video and audio to the network, and receive from a remote source.

Frames are plain byte buffers, so anything that can produce pixels can send: a camera frame, a rendered surface, a decoded file, or something you drew yourself. The library has no opinion about where the bytes came from.


Platform support

| Platform | Status | |---|---| | iOS (physical device) | Supported | | iOS Simulator | Not supported — see below | | Android | Not yet — the build is wired but unverified, and needs an Android NDI SDK | | visionOS | Not yet — the podspec claims the platform but does not link a visionOS library |

The iOS Simulator is not supported on Apple Silicon. The NDI SDK for Apple ships libndi_ios.a as a fat x86_64 + arm64 archive, and the arm64 slice is the device one — there is no arm64-simulator slice to link against. The library compiles for the simulator with NDI stubbed out, and every entry point throws a descriptive error there. Test on a physical device.


Prerequisites


NDI SDK Setup

The NDI SDK is proprietary and cannot be included in this repository. Obtain it directly from NDI: https://ndi.video/for-developers/ndi-sdk/

The SDK lives outside this library. The build autodetects the standard installation path, so you only need to intervene if you installed it somewhere non-standard:

export NDI_SDK_PATH="/path/to/your/ndi-sdk"

Add that to your shell profile, or set it as a CI secret.

| Host | Default path | |---|---| | macOS | /Library/NDI SDK for Apple |

Expected layout

Both the native installer layout and a normalized layout are accepted:

$NDI_SDK_PATH/
├── include/                  ← NDI headers (Processing.NDI.*.h)
└── lib/
    └── iOS/
        └── libndi_ios.a
$NDI_SDK_PATH/
├── include/
└── ios/
    └── libndi_ios.a

iOS

Once the SDK is in place, run:

cd your-app/ios && pod install

If the library cannot be found, pod install fails with a descriptive error.

Android

Android is not usable yet. The Gradle build reads NDI_SDK_PATH and expects $NDI_SDK_PATH/android/<abi>/libndi.so, but the NDI SDK for Apple does not ship Android libraries — its lib/ contains only iOS, macOS, tvOS and visionOS. An Android build therefore fails to link against a default macOS installation.

Sourcing an Android-capable SDK, confirming which NDI distribution provides it, and verifying a build are outstanding. Discovery additionally needs manifest permissions and a WifiManager.MulticastLock, neither of which is in place, so getSources() would return empty even once it links.


API

NdiFinder

Discovers NDI sources on the local network (and optionally on specific IP addresses).

import { NdiFinder, NdiSource } from 'react-native-nitro-ndi'
import { NitroModules } from 'react-native-nitro-modules'

const finder = NitroModules.createHybridObject<NdiFinder>('NdiFinder')
finder.start()                        // begin discovery (optional: pass extra IPs)
const sources: NdiSource[] = finder.getSources()
finder.stop()

| Method | Signature | Description | |---|---|---| | start | (extraIps?: string) => void | Start NDI source discovery. Optionally pass a comma-separated list of additional IP addresses to scan. | | stop | () => void | Stop discovery and release resources. | | getSources | () => NdiSource[] | Returns currently discovered sources ({ name: string, urlAddress: string }). |

Discovery takes a moment to populate — getSources() immediately after start() will usually return an empty array. Poll it, or call it in response to user action.

NdiSender

Sends NDI video and audio frames to the network.

import { NdiSender, NdiFourCC } from 'react-native-nitro-ndi'
import { NitroModules } from 'react-native-nitro-modules'

const sender = NitroModules.createHybridObject<NdiSender>('NdiSender')
sender.sourceName = 'My Camera'       // set before the first send
sender.targetFps = 30

// `data` is an ArrayBuffer of packed pixels, from any source.
if (sender.shouldSendFrame()) {
  sender.sendVideoFrame({
    data, width, height,
    frameRateN: 30000, frameRateD: 1001,
    fourCC: NdiFourCC.BGRA,
    lineStrideBytes: width * 4,
  })
}

sender.close()                        // release the name when done

| Property / Method | Signature | Description | |---|---|---| | sourceName | string | The NDI source name broadcast on the network. Assigning recreates the underlying sender, so receivers reconnect. | | sendVideoFrame | (frame: NdiVideoFrame) => void | Send a raw video frame. Opens the sender on first call. | | sendAudioFrame | (frame: NdiAudioFrame) => void | Send a raw audio frame (32-bit float planar). Never rate-limited. | | shouldSendFrame | () => boolean | Whether targetFps would admit a frame now. Cheap pre-check so a producer can skip conversion work it would only discard. sendVideoFrame applies the same limit itself. | | close | () => void | Release the sender and its name immediately. Sending again reopens it. | | targetFps | number | Caps video frames per second forwarded to NDI. 0 (default) forwards every frame. |

Two senders cannot share a name. NDI refuses to create a sender whose name is already present on the network, so give each one its own. The sender is opened lazily on the first send, which means you can assign sourceName freely before then without touching the network.

Call close() rather than relying on garbage collection. NDI will refuse the name while an undestroyed sender still holds it, so a component that tears down and reopens under the same name must close deterministically.

Pixel formats

| NdiFourCC | Layout | |---|---| | BGRA | 32-bit, blue-green-red-alpha | | BGRX | 32-bit, alpha byte ignored | | RGBA | 32-bit, red-green-blue-alpha | | RGBX | 32-bit, alpha byte ignored | | NV12 | 8-bit Y plane + interleaved CbCr — half the bytes of BGRA | | UYVY | YCbCr 4:2:2 packed |

lineStrideBytes is bytes per row including any row padding, which is not always width * 4 — a camera buffer often pads rows to an alignment boundary. Read the stride from your source rather than computing it. For NV12 the stride is the Y plane's, i.e. width.

NdiReceiver

Receives NDI video and audio frames from a remote source.

import { NdiReceiver } from 'react-native-nitro-ndi'
import { NitroModules } from 'react-native-nitro-modules'

const receiver = NitroModules.createHybridObject<NdiReceiver>('NdiReceiver')
receiver.connect(source)

const frame = await receiver.captureVideoFrame(100)  // null if nothing arrived
if (frame != null) {
  // frame.data is an ArrayBuffer of packed pixels
}

receiver.disconnect()

| Method | Signature | Description | |---|---|---| | connect | (source: NdiSource) => void | Connect to an NDI source. | | disconnect | () => void | Disconnect and release the receiver. Returns immediately; a capture already in flight finishes on its own. | | captureVideoFrame | (timeoutMs: number) => Promise<NdiVideoFrame \| null> | Capture one video frame, resolving null if none arrives within timeoutMs. | | captureAudioFrame | (timeoutMs: number) => Promise<NdiAudioFrame \| null> | Capture one audio frame, resolving null on timeout. |

Capture runs off the JS thread. NDI blocks for the full timeout when a source stalls, so these resolve asynchronously rather than freezing the UI. Await each capture before issuing the next.

Captured frames hand you NDI's own buffer without copying it, and the buffer returns to NDI when JavaScript releases the frame. Hold frames only as long as you need them: the receiver falls back to copying once too many are outstanding, so hoarding them costs memory rather than stalling the stream, but neither is free.


Development

See DEVELOPMENT.md for the repository layout, the checks, git hooks, and the branching convention. (Contributor docs are not shipped in the published package.)